import code, pdb, inspect, cProfile
import traceback
import wxPython as wx
import sys, re, os, time, shutil
sys.path.append('.')
import swmmr
import wx.lib.mixins.listctrl as listmix
import wx.lib.dialogs as wxdialogs
import threading
import wx.lib.agw.customtreectrl as CT
from datetime import datetime
import pywinauto
import wx.lib.dialogs
from collections import OrderedDict
import ConfigParser

__file__ = os.path.abspath('.')

__resources__ = os.environ.get('_MEIPASS2', os.path.abspath(__file__))

class CascadeNotebook(wx.Notebook):
    def __init__(self, parent, cme_forest, el_class_forest):
        self.parent = parent
        self.cme_forest = cme_forest
        self.el_class_forest = el_class_forest
        wx.Notebook.__init__(self, parent = self.parent, size = (450, 300))

class CheckListCtrl(wx.ListCtrl, listmix.CheckListCtrlMixin, listmix.ListCtrlAutoWidthMixin):
    def __init__(self, parent, style):
        self.parent = parent
        wx.ListCtrl.__init__(self, parent, style=style)
        listmix.CheckListCtrlMixin.__init__(self)
        listmix.ListCtrlAutoWidthMixin.__init__(self)

class CascadeDialog(wx.Dialog):
    def __init__(self, parent, swmmrh, title):
        wx.Dialog.__init__(self, parent, -1, title=title, size=(1200, 600), style=wx.DEFAULT_DIALOG_STYLE)
        self.swmmrh = swmmrh
        self.current_cme_model = None
        self.current_cme_elclasses = None
        self.cme_cols = self.swmmrh.getColumnNames('#Cascading_ModelElements')

        self.models_dict = {}
        self.elclasses_dict = {}
        self.max_class_depth = 0
        self.model_max_class_depths = {}
        self.PopulateModelElClassDicts()

        self.nb = wx.Notebook(parent = self, size = (450, 300))

        model_panel = wx.Panel(self.nb)
        self.model_tree = CT.CustomTreeCtrl(model_panel, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
                                            style=wx.SUNKEN_BORDER,
                                            agwStyle=CT.TR_HAS_BUTTONS 
                                                     | CT.TR_HAS_VARIABLE_ROW_HEIGHT 
                                                     | CT.TR_MULTIPLE
                                                     #| CT.TR_AUTO_TOGGLE_CHILD
                                                     #| CT.TR_AUTO_CHECK_CHILD
                                                     )

        self.PopulateModelTree()
        self.model_tree.Bind(CT.EVT_TREE_ITEM_CHECKED, self.OnModelTreeCheck)
        self.model_tree.Bind(wx.EVT_LEFT_DCLICK, self.OnModelTreeDClick)
        self.nb.AddPage(model_panel, 'Model Selection')

        elclass_panel = wx.Panel(self.nb)
        self.elclass_tree = CT.CustomTreeCtrl(elclass_panel, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize,
                                             style=wx.SUNKEN_BORDER,
                                             agwStyle=CT.TR_HAS_BUTTONS 
                                                      | CT.TR_HAS_VARIABLE_ROW_HEIGHT 
                                                      | CT.TR_MULTIPLE
                                                      #| CT.TR_AUTO_TOGGLE_CHILD
                                                      #| CT.TR_AUTO_CHECK_CHILD
                                                      )
        self.PopulateElClassTree()
        self.elclass_tree.Bind(CT.EVT_TREE_ITEM_CHECKED, self.OnElClassTreeCheck)
        self.elclass_tree.Bind(wx.EVT_LEFT_DCLICK, self.OnElClassTreeDClick)
        self.nb.AddPage(elclass_panel, 'Element Class Selection')

        model_vSizer = wx.BoxSizer(wx.VERTICAL)
        model_vSizer.Add(self.model_tree, proportion=1, flag=wx.EXPAND | wx.ALL, border = 0)
        model_panel.SetSizer(model_vSizer)
        model_panel.Layout()

        elclass_vSizer = wx.BoxSizer(wx.VERTICAL)
        elclass_vSizer.Add(self.elclass_tree, proportion=1, flag=wx.EXPAND | wx.ALL, border = 0)
        elclass_panel.SetSizer(elclass_vSizer)
        elclass_panel.Layout()

        self.cme_list = CheckListCtrl(parent = self, 
                                       style = wx.LC_REPORT 
                                       | wx.LC_SINGLE_SEL 
                                       | wx.BORDER_SUNKEN)
        
        self.cme_list.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnCMEItemActivated)

        self.cme_list.OnCheckItem = self.OnCMEListCheckItem
        self.CMESubsetDataMap = {}
        line = wx.StaticLine(self, style=wx.LI_HORIZONTAL)
        OKbtn = wx.Button(self, wx.ID_OK)
        cancelbtn = wx.Button(self, wx.ID_CANCEL)

        hSizer = wx.BoxSizer(wx.HORIZONTAL)
        hSizer.Add(self.nb, proportion = 1, flag = wx.EXPAND | wx.ALL, border = 5)
        hSizer.Add(self.cme_list, proportion = 4, flag = wx.EXPAND | wx.ALL, border = 5)

        vSizer = wx.BoxSizer(wx.VERTICAL)
        vSizer.Add(hSizer, proportion = 1, flag = wx.EXPAND | wx.ALL, border = 5)
        vSizer.Add(line, proportion = 0, flag = wx.EXPAND | wx.ALL, border = 5)
        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
        btnsizer.Add(OKbtn, proportion = 1, flag = wx.ALL)
        btnsizer.Add(cancelbtn, proportion = 1, flag = wx.ALL)
        vSizer.Add(btnsizer, proportion = 0, flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.ALL, border = 5)

        self.SetSizer(vSizer)
        self.Layout()
    
    def PopulateModelElClassDicts(self):
        cme = self.swmmrh.CME_GetSubset()
        for row in cme:
            cme_id = row[self.cme_cols.index('OBJECTID')]
            modelname = row[self.cme_cols.index('ModelName')]
            classes = [row[self.cme_cols.index('Class01')],
                       row[self.cme_cols.index('Class02')],
                       row[self.cme_cols.index('Class03')],
                       row[self.cme_cols.index('Class04')],
                       row[self.cme_cols.index('Class05')],
                       row[self.cme_cols.index('Class06')]]
            commit = row[self.cme_cols.index('CommitValue')]

            current_class_depth = len([class_name for class_name in classes if class_name is not None])

            if current_class_depth > self.max_class_depth:
                self.max_class_depth = current_class_depth

            if modelname not in self.model_max_class_depths.keys():
                self.model_max_class_depths[modelname] = current_class_depth
            elif current_class_depth > self.model_max_class_depths[modelname]:
                self.model_max_class_depths[modelname] = current_class_depth

            previous_class = None
            previous_elclass_level = None
            current_elclass_level = self.elclasses_dict

            if modelname not in self.models_dict.keys():
                self.models_dict[modelname] = {}

            previous_model_level = None
            current_model_level = self.models_dict[modelname]

            for i, elclass in enumerate(classes):
                if elclass is None:
                    if current_elclass_level is None:
                        previous_elclass_level[previous_class] = [modelname]
                    else:
                        if modelname not in previous_elclass_level[previous_class]:
                            previous_elclass_level[previous_class].append(modelname)
                    break
                else:
                    if current_elclass_level is None:
                        previous_elclass_level[previous_class] = {elclass : None}
                        previous_elclass_level = previous_elclass_level[previous_class]
                        current_elclass_level = None
                    elif elclass not in current_elclass_level.keys():
                        current_elclass_level[elclass] = None
                        previous_elclass_level = current_elclass_level
                        current_elclass_level = None
                    else:
                        previous_elclass_level = current_elclass_level
                        current_elclass_level = current_elclass_level[elclass]

                    if current_model_level is None:
                        previous_model_level[previous_class] = {elclass : None}
                        previous_model_level = previous_model_level[previous_class]
                        current_model_level = None
                    elif elclass not in current_model_level.keys():
                        current_model_level[elclass] = None
                        previous_model_level = current_model_level
                        current_model_level = None
                    else:
                        previous_model_level = current_model_level
                        current_model_level = current_model_level[elclass]

                    previous_class = elclass

    def PopulateTree(self, tree, tree_parent, dict_parent, child_name):
        child_node = tree.AppendItem(tree_parent, child_name, ct_type=1)
        tree.SetPyData(child_node, None)
        tree.SetItemImage(child_node, 24, CT.TreeItemIcon_Normal)
        tree.SetItemImage(child_node, 13, CT.TreeItemIcon_Expanded)

        if not isinstance(dict_parent, list):
            grandchildren = dict_parent[child_name]
            if grandchildren is not None:
                if isinstance(grandchildren, list):
                    gc_list = grandchildren
                else:
                    gc_list = grandchildren.keys()

                for grandchild in gc_list:
                    self.PopulateTree(tree = tree, tree_parent = child_node, dict_parent = grandchildren, child_name = grandchild) 

    def PopulateModelTree(self):
        self.model_tree.root = self.model_tree.AddRoot("All Models", ct_type = 1)
        self.model_tree.SetPyData(self.model_tree.root, None)
        self.model_tree.SetItemImage(self.model_tree.root, 24, CT.TreeItemIcon_Normal)
        self.model_tree.SetItemImage(self.model_tree.root, 13, CT.TreeItemIcon_Expanded)

        for model in self.models_dict.keys():
            self.PopulateTree(tree = self.model_tree, tree_parent = self.model_tree.root, dict_parent = self.models_dict, child_name = model)

        self.model_tree.Expand(self.model_tree.root)

    def PopulateElClassTree(self):
        self.elclass_tree.root = self.elclass_tree.AddRoot("All Element Classes", ct_type = 1)
        self.elclass_tree.SetPyData(self.elclass_tree.root, None)
        self.elclass_tree.SetItemImage(self.elclass_tree.root, 24, CT.TreeItemIcon_Normal)
        self.elclass_tree.SetItemImage(self.elclass_tree.root, 13, CT.TreeItemIcon_Expanded)

        for elclass in self.elclasses_dict.keys():
            self.PopulateTree(tree = self.elclass_tree, tree_parent = self.elclass_tree.root, dict_parent = self.elclasses_dict, child_name = elclass)

        self.elclass_tree.Expand(self.elclass_tree.root)

    def OnModelTreeDClick(self, event):
        pt = event.GetPosition()
        item, flags = self.model_tree.HitTest(pt)
        model_name = None
        if item is not None:
            if item == self.model_tree.root:
                self.PopulateCMEList()
            else:
                class_list = []
                current_item = item
                while self.model_tree.GetItemParent(current_item) != self.model_tree.root:
                    class_name = current_item.GetText()
                    class_list.append(class_name)
                    current_item = self.model_tree.GetItemParent(current_item)

                model_name = current_item.GetText()

                if len(class_list) > 0:
                    class_list = class_list[::-1] #reverse class list so top class is at the front
                else:
                    class_list = None

                self.PopulateCMEList(model = model_name, elclasses = class_list)
        else:
            event.Skip()

    def OnElClassTreeDClick(self, event):
        pt = event.GetPosition()
        item, flags = self.elclass_tree.HitTest(pt)
        if item is not None:
            if item == self.elclass_tree.root:
                self.PopulateCMEList()
            else:
                class_list = []
                current_item = item
                model_name = None
                if not self.elclass_tree.HasChildren(current_item):
                    model_name = current_item.GetText()
                    current_item = self.elclass_tree.GetItemParent(current_item)

                while current_item != self.elclass_tree.root:
                    class_name = current_item.GetText()
                    class_list.append(class_name)
                    current_item = self.model_tree.GetItemParent(current_item)

                if len(class_list) > 0:
                    class_list = class_list[::-1] #reverse class list so top class is at the front
                else:
                    class_list = None

                self.PopulateCMEList(model = model_name, elclasses = class_list)
        else:
            event.Skip()

    def getCMESubsetDataMap(self, model = None, elclasses = None):
        data = self.swmmrh.CME_GetSubset(model, elclasses)
        return dict([(i, row) for i, row in enumerate(data)])

    def PopulateCMEList(self, model = None, elclasses=None):
        col_names_widths = [('Element Name', 200), ('Model', 100), ('Parameters affected', 200)]

        if elclasses is None:
            num_elclasses = 0
        else:
            num_elclasses = len(elclasses)

        self.cme_list.DeleteAllColumns()

        table_cols = []
        for i, tup in enumerate(col_names_widths):
            name, width = tup
            self.cme_list.InsertColumn(i, name)
            if width is not None:
                self.cme_list.SetColumnWidth(i, width)
            table_cols.append(name)

        self.current_cme_model = model
        self.current_cme_elclasses = elclasses
        self.cme_list.DeleteAllItems()
        self.CMESubsetDataMap = self.getCMESubsetDataMap(model, elclasses)

        if len(self.CMESubsetDataMap) > 0:
            items = self.CMESubsetDataMap.iteritems()
            for j, tup in enumerate(items):
                data = tup[1]
                
                elname_ix = self.cme_cols.index('ElementName')
                model_ix = self.cme_cols.index('ModelName')
                commit_ix = self.cme_cols.index('CommitValue')
                fieldchanges_ix = self.cme_cols.index('FieldDifferences')

                class_names = ['Class01', 'Class02', 'Class03', 'Class04', 'Class05', 'Class06']
                class_ixs = [self.cme_cols.index(class_name) for class_name in class_names]
                
                row = self.cme_list.InsertStringItem(j, str(data[elname_ix]))
                self.cme_list.SetItemData(row, j)
                self.cme_list.SetStringItem(row, table_cols.index('Model'), str(data[model_ix]))
                self.cme_list.SetStringItem(row, table_cols.index('Parameters affected'), str(data[fieldchanges_ix]))

                class_names_ixs = zip(class_names, class_ixs)
                for i in xrange(num_elclasses, 6):
                    class_name, class_ix = class_names_ixs[i]

                    if data[class_ix] is not None:
                        if class_name not in table_cols:
                            table_cols.insert(table_cols.index('Parameters affected'), class_name)
                            col_pos = table_cols.index(class_name)
                            self.cme_list.InsertColumn(col_pos, class_name)
                            self.cme_list.SetColumnWidth(col_pos, 100)

                        col_pos = table_cols.index(class_name)
                        self.cme_list.SetStringItem(row, table_cols.index(class_name), str(data[class_ix]))

                if data[commit_ix]:
                    self.cme_list.CheckItem(row)

    def refreshCME(self):
        self.PopulateCMEList(model=self.current_cme_model, elclasses=self.current_cme_elclasses)

    def OnCMEItemActivated(self, event):
        index = event.m_itemIndex
        dataMap_num = self.cme_list.GetItemData(index)
        data = self.CMESubsetDataMap[dataMap_num]

        cme_elname = data[self.cme_cols.index('ElementName')]
        cme_model = data[self.cme_cols.index('ModelName')]
        cme_objectid = data[self.cme_cols.index('OBJECTID')]
        cme_elclass = data[self.cme_cols.index('ElementClass')]

        dlg = wx.Dialog(self, -1, title='Element details', size=(800, 500), style=wx.DEFAULT_DIALOG_STYLE)

        font = wx.Font(8, family = wx.FONTFAMILY_DEFAULT, style =  wx.NORMAL, weight = wx.FONTWEIGHT_BOLD)
        grid1 = wx.GridBagSizer(hgap=10, vgap=5)
        elnamelbl = wx.StaticText(dlg, label='Element: ')
        elnamelbl.SetFont(font)
        elnameval = wx.StaticText(dlg, label=cme_elname)
        grid1.Add(elnamelbl, pos=(0,0))
        grid1.Add(elnameval, pos=(0,1))

        elclasslbl = wx.StaticText(dlg, label='Class: ')
        elclasslbl.SetFont(font)
        elclassval = wx.StaticText(dlg, label=cme_elclass)
        grid1.Add(elclasslbl, pos=(1,0))
        grid1.Add(elclassval, pos=(1,1))

        modellbl = wx.StaticText(dlg, label='Model: ')
        modellbl.SetFont(font)
        modelval = wx.StaticText(dlg, label=cme_model)
        grid1.Add(modellbl, pos=(2,0))
        grid1.Add(modelval, pos=(2,1))

        grid = wx.GridBagSizer(hgap=15, vgap=10)
        paramlbl = wx.StaticText(dlg, label = 'Parameter')
        paramlbl.SetFont(font)
        grid.Add(paramlbl, pos=(0,0), flag = wx.ALIGN_CENTER)
        oldlbl = wx.StaticText(dlg, label='Current value')
        oldlbl.SetFont(font)
        grid.Add(oldlbl, pos=(0,1), flag = wx.ALIGN_CENTER)
        newlbl = wx.StaticText(dlg, label='New value')
        newlbl.SetFont(font)
        grid.Add(newlbl, pos=(0,2), flag = wx.ALIGN_CENTER)

        fieldchanges = self.swmmrh.CME_GetFieldChanges(cme_id = cme_objectid)
        for i, row in enumerate(fieldchanges):
            field, currentval, newval = row
            if currentval is None:
                currentval = ''
            if newval is None:
                newval = ''
            fieldlbl = wx.StaticText(dlg, label=field)
            grid.Add(fieldlbl, pos=(i+1, 0), flag = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)
            
            currentvallbl = wx.StaticText(dlg, label=currentval)
            grid.Add(currentvallbl, pos=(i+1, 1), flag = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)

            newvallbl = wx.StaticText(dlg, label=newval)
            grid.Add(newvallbl, pos=(i+1, 2), flag = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)

        vSizer = wx.BoxSizer(wx.VERTICAL)
        vSizer.Add(grid1, 0, wx.GROW | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 10)
        vSizer.Add(grid, 0, wx.GROW | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 10)
    
        dlg.SetSizer(vSizer)
        vSizer.Fit(dlg)

        dlg.CenterOnScreen()
        dlg.ShowModal()
        dlg.Destroy()

    def TreeCheckSyncUp(self, tree_name, item):
        if tree_name == 'elclass':
            tree = self.elclass_tree
        elif tree_name == 'model':
            tree = self.model_tree
        else:
            sys.exit('unknown tree')

        if item != tree.root:
            parent = tree.GetItemParent(item)
            children = parent.GetChildren()
            some_children_checked = False
            all_children_checked = True
            children_states = []
            for item in children:
                item.Set3State(True)
                item_state = item.Get3StateValue()
                item.Set3State(False)
                children_states.append(item_state)
                if item_state != wx.CHK_CHECKED:
                    all_children_checked = False
                if item_state != wx.CHK_UNCHECKED:
                    some_children_checked = True
            parent.Set3State(True)
            if all_children_checked:
                parent.Set3StateValue(wx.CHK_CHECKED)
            elif some_children_checked:
                parent.Set3StateValue(wx.CHK_UNDETERMINED)
            else:
                parent.Set3StateValue(wx.CHK_UNCHECKED)
            parent.Set3State(False)

            if parent == tree.root:
                tree.Refresh()
                return
            else:
                self.TreeCheckSyncUp(tree_name, parent)

    def TreeCheckSyncDown(self, tree_name, item):
        if tree_name == 'elclass':
            tree = self.elclass_tree
        elif tree_name == 'model':
            tree = self.model_tree
        else:
            sys.exit('unknown tree')

        children = item.GetChildren()
        if len(children) > 0:
            item_state = item.GetValue()
            for child in children:
                child.Set3StateValue(item_state)
                self.TreeCheckSyncDown(tree_name, child)
        else:
            tree.Refresh()


    def TreeCheckLeaf(self, tree_name, root, name, check_state):
        if re.search(name, root.GetText()):
            root.Set3StateValue(check_state)
            self.TreeCheckSyncUp(tree_name, root)
        else:
            for child in root.GetChildren():
                self.TreeCheckLeaf(tree_name, child, name, check_state)

    def OnModelTreeCheck(self, event):
        item = event.GetItem()
        check_state = int(item.GetValue())

        class_list = []
        current_item = item
        if current_item == self.model_tree.root:
            class_list = None
            model_name = None
        else:
            while self.model_tree.GetItemParent(current_item) != self.model_tree.root:
                class_list.append(current_item.GetText())
                current_item = self.model_tree.GetItemParent(current_item)

            model_name = current_item.GetText()
            class_list = class_list[::-1]

        self.swmmrh.CME_SetSubsetState(commit_value = check_state, model_name = model_name, elclasses = class_list)
        self.TreeCheckSyncUp('model', item)
        self.TreeCheckSyncDown('model', item)

        elclass_tree_item = self.elclass_tree.root
        if class_list is not None:
            for elclass in class_list:
                elclass_tree_item = self.elclass_tree.FindItem(elclass_tree_item, elclass)

        if elclass_tree_item == self.elclass_tree.root:
            self.elclass_tree.root.Set3StateValue(check_state)
            self.TreeCheckSyncDown('elclass', self.elclass_tree.root)
        else:
            self.TreeCheckLeaf('elclass', elclass_tree_item, model_name, check_state)

        self.refreshCME()

    def OnElClassTreeCheck(self, event):
        item = event.GetItem()
        check_state = int(item.GetValue())

        class_list = []
        current_item = item
        if current_item == self.elclass_tree.root:
            class_list = None
            model_name = None
        else:
            if not current_item.HasChildren():
                model_name = current_item.GetText()
                current_item = self.elclass_tree.GetItemParent(current_item)
            else:
                model_name = None

            while current_item != self.elclass_tree.root:
                class_list.append(current_item.GetText())
                current_item = self.elclass_tree.GetItemParent(current_item)
            
            class_list = class_list[::-1]

        self.swmmrh.CME_SetSubsetState(commit_value = check_state, model_name = model_name, elclasses = class_list)
        self.TreeCheckSyncUp('elclass', item)
        self.TreeCheckSyncDown('elclass', item)

        if model_name is None and class_list is not None:
            for model_item in self.model_tree.root.GetChildren():
                current_item = model_item
                for elclass in class_list:
                    current_item = self.model_tree.FindItem(current_item, elclass)
                    if current_item is None:
                        break

                if current_item.GetText() == class_list[-1]:
                    current_item.Set3StateValue(check_state)
                    self.TreeCheckSyncUp('model', current_item)
                    self.TreeCheckSyncDown('model', current_item)

        elif model_name is None and class_list is None:
            self.model_tree.root.Set3StateValue(check_state)
            self.TreeCheckSyncDown('model', self.model_tree.root)
        elif model_name is not None and class_list is not None:
            current_item = self.model_tree.FindItem(self.model_tree.root, model_name)
            for elclass in class_list:
                current_item = self.model_tree.FindItem(current_item, elclass)
            current_item.Set3StateValue(check_state)
            self.TreeCheckSyncUp('model', current_item)
            self.TreeCheckSyncDown('model', current_item)

        self.refreshCME()

    def OnCMEListCheckItem(self, index, flag):
        dataMap_num = self.cme_list.GetItemData(index)
        data = self.CMESubsetDataMap[dataMap_num]
        cme_id = data[self.cme_cols.index('OBJECTID')]
        model_name = data[self.cme_cols.index('ModelName')]
        classes = [data[self.cme_cols.index('Class01')],
                   data[self.cme_cols.index('Class02')],
                   data[self.cme_cols.index('Class03')],
                   data[self.cme_cols.index('Class04')],
                   data[self.cme_cols.index('Class05')],
                   data[self.cme_cols.index('Class06')]]

        classes = [class_name for class_name in classes if class_name is not None]

        cme_commit = self.swmmrh.CME_GetSubsetState(cme_id = cme_id)
        
        if flag != cme_commit:
            self.swmmrh.CME_SetSubsetState(int(flag), cme_id = cme_id)
            check_state = self.swmmrh.CME_GetSubsetState(model_name = model_name, elclasses = classes)

            current_item = self.model_tree.FindItem(self.model_tree.root, model_name)
            for elclass in classes:
                current_item = self.model_tree.FindItem(current_item, elclass)

            current_item.Set3State(True)
            current_item.Set3StateValue(check_state)
            current_item.Set3State(False)
            self.TreeCheckSyncUp('model', current_item)

            current_item = self.elclass_tree.root
            for elclass in classes:
                current_item = self.elclass_tree.FindItem(current_item, elclass)

            current_item = self.elclass_tree.FindItem(current_item, model_name)

            current_item.Set3State(True)
            current_item.Set3StateValue(check_state)
            current_item.Set3State(False)
            self.TreeCheckSyncUp('elclass', current_item)

class INPPathValidator(wx.PyValidator):
    def __init__(self):
        wx.PyValidator.__init__(self)

    def Clone(self):
        return INPPathValidator()

    def Validate(self, win):
        textCtrl = self.GetWindow()
        path = textCtrl.GetValue()

        if not os.path.exists(path):
            wx.MessageBox("No such file.", "Error")
            textCtrl.Refresh()
            return False
        else:
            if os.path.isfile(path):
                fname = str(os.path.split(path)[1])
                re_str = re.compile('.inp$', re.IGNORECASE)
                if re.search(re_str, fname):
                    return True
                else:
                    wx.MessageBox("Invalid file type supplied.", "Error")
                    textCtrl.Refresh()
                    return False

    def TransferToWindow(self):
        return True

    def TransferFromWindow(self):
        return True

class SWMMPathValidator(wx.PyValidator):
    def __init__(self):
        wx.PyValidator.__init__(self)

    def Clone(self):
        return SWMMPathValidator()

    def Validate(self, win):
        textCtrl = self.GetWindow()
        path = textCtrl.GetValue()

        if path != '' and not os.path.exists(path):
            wx.MessageBox("That's not a valid path.", "Error")
            textCtrl.Refresh()
            return False
        else:
            return True

    def TransferToWindow(self):
        return True

    def TransferFromWindow(self):
        return True

class TextValidator(wx.PyValidator):
    def __init__(self):
        wx.PyValidator.__init__(self)

    def Clone(self):
        return TextValidator()

    def Validate(self, win):
        textCtrl = self.GetWindow()
        text = textCtrl.GetValue()

        if len(text) > 8000:
            wx.MessageBox("Text is " + str(len(text) - 8000) + " characters too long.")
            return False
        if not all([ord(ch) < 128 for ch in text]):
            wx.MessageBox("Text contains non-ASCII characters", "Error")
            return False
        else:
            return True

    def TransferToWindow(self):
        return True

    def TransferFromWindow(self):
        return True

class TagValidator(TextValidator):
    def __init__(self):
        TextValidator.__init__(self)

    def Clone(self):
        return TagValidator()

    def Validate(self, win):
        TextValidator.Validate(self, win)
        textCtrl = self.GetWindow()
        text = textCtrl.GetValue()

        tags_list = text.split(',')
        tags_list = [tag.strip() for tag in tags_list]
        if len([tag for tag in tags_list if re.search(' ', tag)]) > 0:
            wx.MessageBox("Tags should be separated by commas and should not contain spaces.")
            return False
        else:
            return True

    def TransferToWindow(self):
        return True

    def TransferFromWindow(self):
        return True

class VersionValidator(TextValidator):
    def __init__(self):
        TextValidator.__init__(self)

    def Clone(self):
        return VersionValidator()

    def Validate(self, win):
        TextValidator.Validate(self, win)
        textCtrl = self.GetWindow()
        text = textCtrl.GetValue()

        if len(text) > 200:
            wx.MessageBox("Version text is over the 200 character limit.")
            return False
        else:
            return True

    def TransferToWindow(self):
        return True

    def TransferFromWindow(self):
        return True

class ListCtrlAutoWidth(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin):
    def __init__(self, *args, **kwargs):
        wx.ListCtrl.__init__(self, *args, **kwargs)
        listmix.ListCtrlAutoWidthMixin.__init__(self)

class ProportionalSplitter(wx.SplitterWindow):
        def __init__(self,parent, id = -1, proportion=0.66, minsize=50, maxsize = None, size = wx.DefaultSize, **kwargs):
                wx.SplitterWindow.__init__(self,parent,id,wx.Point(0, 0),size, **kwargs)
                self.SetMinimumPaneSize(minsize) #the minimum size of a pane.
                self.maxsize = maxsize
                self.proportion = proportion
                if not 0 < self.proportion < 1:
                        raise ValueError, "proportion value for ProportionalSplitter must be between 0 and 1."
                self.ResetSash()
                self.Bind(wx.EVT_SIZE, self.OnReSize)
                self.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGED, self.OnSashChanged, id=id)
                ##hack to set sizes on first paint event
                self.Bind(wx.EVT_PAINT, self.OnPaint)
                self.firstpaint = True

        def SplitHorizontally(self, win1, win2):
                if self.GetParent() is None: return False
                return wx.SplitterWindow.SplitHorizontally(self, win1, win2,
                        int(round(self.GetParent().GetSize().GetHeight() * self.proportion)))

        def SplitVertically(self, win1, win2):
                if self.GetParent() is None: return False
                return wx.SplitterWindow.SplitVertically(self, win1, win2,
                        int(round(self.GetParent().GetSize().GetWidth() * self.proportion)))

        def GetExpectedSashPosition(self):
            if self.GetSplitMode() == wx.SPLIT_HORIZONTAL:
                if self.maxsize is None:
                    maxsize = self.GetParent().GetClientSize().height
                else:
                    maxsize = self.maxsize
                tot = max(self.GetMinimumPaneSize(), maxsize)
            else:
                if self.maxsize is None:
                    maxsize = self.GetParent().GetClientSize().width
                else:
                    maxsize = self.maxsize
                tot = max(self.GetMinimumPaneSize(), maxsize)
            return int(round(tot * self.proportion))

        def ResetSash(self):
                self.SetSashPosition(self.GetExpectedSashPosition())

        def OnReSize(self, event):
                "Window has been resized, so we need to adjust the sash based on self.proportion."
                self.ResetSash()
                event.Skip()

        def OnSashChanged(self, event):
                "We'll change self.proportion now based on where user dragged the sash."
                pos = float(self.GetSashPosition())
                if self.GetSplitMode() == wx.SPLIT_HORIZONTAL:
                        tot = max(self.GetMinimumPaneSize(),self.GetParent().GetClientSize().height)
                else:
                        tot = max(self.GetMinimumPaneSize(),self.GetParent().GetClientSize().width)
                self.proportion = pos / tot
                event.Skip()

        def OnPaint(self,event):
                if self.firstpaint:
                        if self.GetSashPosition() != self.GetExpectedSashPosition():
                                self.ResetSash()
                        self.firstpaint = False
                event.Skip()

class MasterFrame(wx.Frame, listmix.ColumnSorterMixin):
    def __init__(self):
        wx.Frame.__init__(self, None, -1, title='SWMMR', size=(1400,600))

        # state
        self.swmmrh = None
        self.isconnected = False
        self.isbusy = False
        self.defaultcfg_path = os.path.join(__resources__, 'swmmr.ini')
        self.usercfg_path = os.path.join(os.path.expanduser('~'), '_swmmr')

        icons_resource = os.path.join(__resources__, 'icons')
        ico = wx.Icon(os.path.join(icons_resource, 'swmmr.ico'), wx.BITMAP_TYPE_ICO)
        self.SetIcon(ico)

        self.MENU_CONNECT = 101
        self.MENU_DISCONNECT = 102
        self.MENU_REFRESH = 103
        self.MENU_BUILD = 104
        self.MENU_SWMMPATHS = 105
        self.MENU_ABOUT = 106
        self.MENU_CLOSE = 107
        self.MENU_SWMMPATH = 108
        self.MENU_COMMIT = 109

        self.mb = wx.MenuBar()
        self.file_menu = wx.Menu()
        file_menu_items = ((self.MENU_CONNECT, '&Connect'),
                           (self.MENU_DISCONNECT, '&Disconnect'),
                           (self.MENU_REFRESH, '&Refresh'),
                           (self.MENU_BUILD, '&Build Repository'),
                           (self.MENU_SWMMPATH, '&Set SWMM GUI path'))
        for item in file_menu_items:
            self.file_menu.Append(*item)
            self.Bind(wx.EVT_MENU, self.OnMenu, id = item[0])
        self.file_menu.AppendSeparator()
        commit_item = wx.MenuItem(self.file_menu, self.MENU_COMMIT, "&Commit")
        commit_item.SetBitmap(wx.ArtProvider.GetBitmap(wx.ART_FILE_SAVE))
        self.file_menu.AppendItem(commit_item)
        self.Bind(wx.EVT_MENU, self.OnMenu, id = self.MENU_COMMIT)
        exit_item = wx.MenuItem(self.file_menu, self.MENU_CLOSE, "&Exit")
        exit_item.SetBitmap(wx.ArtProvider.GetBitmap(wx.ART_QUIT))
        self.file_menu.AppendItem(exit_item)
        self.Bind(wx.EVT_MENU, self.OnMenu, id = self.MENU_CLOSE)
        self.mb.Append(self.file_menu, "&File")
        self.file_menu.Enable(self.MENU_DISCONNECT, False)
        self.file_menu.Enable(self.MENU_REFRESH, False)
        self.file_menu.Enable(self.MENU_COMMIT, False)

        help_menu = wx.Menu()
        help_menu_items = ((self.MENU_ABOUT, '&About'), )
        for item in help_menu_items:
            help_menu.Append(*item)
            self.Bind(wx.EVT_MENU, self.OnMenu, id = item[0])
        self.mb.Append(help_menu, "&Help")

        self.SetMenuBar(self.mb)

        mainpanel = wx.Panel(self)

        self.mainsplitter = ProportionalSplitter(mainpanel, proportion = 0.5, minsize = 185)
        self.modelsplitter = ProportionalSplitter(self.mainsplitter, proportion = 0.5, minsize = 100)

        userlist_panel = wx.Panel(self.modelsplitter)
        list_style = wx.LC_REPORT | wx.LC_SORT_ASCENDING | wx.LC_VRULES | wx.LC_HRULES | wx.LC_SINGLE_SEL
        self.ID_USERLIST = 100
        self.usermodels_list = ListCtrlAutoWidth(userlist_panel, id = self.ID_USERLIST, style = list_style)
        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnUserModelActivated, self.usermodels_list)
        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnModelSelected, self.usermodels_list)
        self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnModelDeselected, self.usermodels_list)
        self.allmodels = {}
        self.current_model = None
        listmix.ColumnSorterMixin.__init__(self, 7)
        self.SortListItems(0, 1)
        umlSizer = wx.BoxSizer(wx.VERTICAL)
        umlSizer.Add(self.usermodels_list, 1, flag = wx.EXPAND | wx.LEFT, border = 5)
        userlist_panel.SetSizer(umlSizer)
        userlist_panel.SetAutoLayout(True)

        excludedlist_panel = wx.Panel(self.modelsplitter)
        self.ID_EXCLUDEDLIST = 200
        self.excludedmodels_list = ListCtrlAutoWidth(excludedlist_panel, id = self.ID_EXCLUDEDLIST, style = list_style | wx.LC_NO_HEADER)
        #self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnExcludedModelActivated, self.excludedmodels_list)
        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnModelSelected, self.excludedmodels_list)
        self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnModelDeselected, self.excludedmodels_list)
        emlSizer = wx.BoxSizer(wx.VERTICAL)
        emlSizer.Add(self.excludedmodels_list, 1, flag = wx.EXPAND | wx.LEFT, border = 5)
        excludedlist_panel.SetSizer(emlSizer)
        excludedlist_panel.SetAutoLayout(True)

        dtime_size = 125
        self.columndef = OrderedDict([('Model', 200), 
                                      ('Latest Version', 200),
                                      ('DocLink', 100), 
                                      ('Created On', dtime_size),
                                      ('Last Modified', dtime_size), 
                                      ('User', 120), 
                                      ('Description', None)])
        for i, colname in enumerate(self.columndef.keys()):
            self.usermodels_list.InsertColumn(i, colname)
            self.excludedmodels_list.InsertColumn(i, colname)
            width = self.columndef[colname]
            if self.columndef[colname] is not None:
                self.usermodels_list.SetColumnWidth(i, width)
                self.excludedmodels_list.SetColumnWidth(i, width)

        self.sidepanel = wx.Panel(self.mainsplitter)

        self.tb = wx.ToolBar(self.sidepanel, -1, size = (-1, 45), style = wx.TB_HORIZONTAL | wx.BORDER | wx.TB_FLAT)
        tsize = (17,17)
        self.tb.SetToolBitmapSize(tsize)
        upload_bmp = wx.Bitmap(os.path.join(icons_resource, 'upload.png'))
        download_bmp = wx.Bitmap(os.path.join(icons_resource, 'download.png'))
        swmm_bmp = wx.Bitmap(os.path.join(icons_resource, 'swmm.png'))
        cascade_bmp = wx.Bitmap(os.path.join(icons_resource, 'cascade.png'))
        commit_bmp = wx.ArtProvider.GetBitmap(wx.ART_FILE_SAVE, wx.ART_TOOLBAR, tsize)
        self.tool_ids = []
        self.version_tool_ids = []
        self.TOOL_COMMIT = 10
        self.tool_ids.append(self.TOOL_COMMIT)
        self.tb.AddLabelTool(self.TOOL_COMMIT, "Save changes", commit_bmp, shortHelp = 'Save changes', longHelp = 'Long')
        self.Bind(wx.EVT_TOOL, self.OnToolClick, id=self.TOOL_COMMIT)
        self.TOOL_UPLOAD = 20
        self.tool_ids.append(self.TOOL_UPLOAD)
        self.tb.AddLabelTool(self.TOOL_UPLOAD, "Upload .INP file", upload_bmp, shortHelp = 'Upload .INP file', longHelp = 'Long')
        self.Bind(wx.EVT_TOOL, self.OnToolClick, id=self.TOOL_UPLOAD)
        self.TOOL_DOWNLOAD = 30
        self.tool_ids.append(self.TOOL_DOWNLOAD)
        self.version_tool_ids.append(self.TOOL_DOWNLOAD)
        self.tb.AddLabelTool(self.TOOL_DOWNLOAD, "Export .INP file", download_bmp, shortHelp = 'Download .INP file', longHelp = 'Long')
        self.Bind(wx.EVT_TOOL, self.OnToolClick, id=self.TOOL_DOWNLOAD)
        self.TOOL_SWMM = 40
        self.tool_ids.append(self.TOOL_SWMM)
        self.version_tool_ids.append(self.TOOL_SWMM)
        self.tb.AddLabelTool(self.TOOL_SWMM, "Open in SWMM GUI", swmm_bmp, shortHelp = 'Open in SWMM GUI', longHelp = 'Long')
        self.Bind(wx.EVT_TOOL, self.OnToolClick, id=self.TOOL_SWMM)
        self.TOOL_CASCADE = 50
        self.tool_ids.append(self.TOOL_CASCADE)
        self.version_tool_ids.append(self.TOOL_CASCADE)
        self.tb.AddLabelTool(self.TOOL_CASCADE, "Cascade model elements", cascade_bmp, shortHelp = 'Cascade model elements', longHelp = 'Long')
        self.Bind(wx.EVT_TOOL, self.OnToolClick, id=self.TOOL_CASCADE)
        for id in self.tool_ids:
            self.tb.EnableTool(id, False)
        self.tb.Realize()
        sidepanelSizer = wx.BoxSizer(wx.VERTICAL)
        sidepanelSizer.Add(self.tb, 0, wx.EXPAND)

        nb = wx.Notebook(self.sidepanel, size = (-1, 500))
        self.currentmodel_versiondata = None
        self.currentversion_ix = None
        versionsplitter = ProportionalSplitter(nb, proportion = 0.9, minsize = 200, maxsize = 50)
        versionsPanel = wx.Panel(versionsplitter)
        self.headingfont = wx.Font(9, wx.FONTFAMILY_DEFAULT, wx.NORMAL, wx.FONTWEIGHT_BOLD)
        versions_lbl = wx.StaticText(versionsPanel, label = 'Current Model Versions')
        versions_lbl.SetFont(self.headingfont)
        self.versions_lb = wx.ListBox(versionsPanel, size = (200, 232), choices = [], style = wx.LB_SINGLE)
        versionsPanel.Bind(wx.EVT_LISTBOX, self.OnVersionSelected, self.versions_lb)
        versionsSizer = wx.BoxSizer(wx.VERTICAL)
        versionsSizer.Add(versions_lbl, 0, flag = wx.BOTTOM | wx.TOP, border = 5)
        versionsSizer.Add(self.versions_lb, 1, flag = wx.EXPAND | wx.BOTTOM, border = 5)
        versionsPanel.SetSizer(versionsSizer)

        # details constants
        self.IX_VERSIONID = 0
        self.IX_TS = 1
        self.IX_USER = 2
        self.IX_VNAME = 3
        self.IX_BUILD = 4
        self.IX_DOC = 5
        self.IX_DESC = 6

        detailsPanel = wx.Panel(versionsplitter)
        details_lbl = wx.StaticText(detailsPanel, label = 'Version Details')
        details_lbl.SetFont(self.headingfont)
        versions_text_size = (200, -1)
        version_label = wx.StaticText(detailsPanel, label = 'Name')
        self.version_text = wx.TextCtrl(detailsPanel, value='', size = versions_text_size)
        self.version_text.Disable()
        swmmbuild_label = wx.StaticText(detailsPanel, label = 'SWMM build')
        self.swmmbuild_choice = wx.Choice(detailsPanel, -1, size = (200, -1), choices = [])
        self.swmmbuild_choice.Disable()
        doc_label = wx.StaticText(detailsPanel, label = 'Documentation')
        self.doc_text = wx.TextCtrl(detailsPanel, value='', size = versions_text_size)
        self.doc_text.Disable()
        des_label = wx.StaticText(detailsPanel, label = 'Description')
        self.des_text = wx.TextCtrl(detailsPanel, value = '', size = (200, 200), style = wx.TE_MULTILINE)
        self.des_text.Disable()
        self.updatedetails_btn = wx.Button(detailsPanel, label = 'Update')
        detailsPanel.Bind(wx.EVT_BUTTON, self.updateModelVersion, self.updatedetails_btn)
        self.updatedetails_btn.Disable()
        self.resetdetails_btn = wx.Button(detailsPanel, label = 'Reset')
        detailsPanel.Bind(wx.EVT_BUTTON, self.fillVersionDetails, self.resetdetails_btn)
        self.resetdetails_btn.Disable()
        def OnDetailsChange(event):
            self.updatedetails_btn.Enable()
            self.resetdetails_btn.Enable()
            for id in self.version_tool_ids:
                self.tb.EnableTool(id, False)
        detailsPanel.Bind(wx.EVT_TEXT, OnDetailsChange, self.version_text)
        detailsPanel.Bind(wx.EVT_TEXT, OnDetailsChange, self.doc_text)
        detailsPanel.Bind(wx.EVT_TEXT, OnDetailsChange, self.des_text)
        detailsPanel.Bind(wx.EVT_CHOICE, OnDetailsChange, self.swmmbuild_choice)
        pane_gap = 6
        detailsSizer = wx.BoxSizer(wx.VERTICAL)
        detailsSizer.Add(details_lbl, 0, flag = wx.BOTTOM | wx.TOP, border = 5)
        detailsSizer.Add(version_label, 0)
        detailsSizer.Add(self.version_text, 0, flag = wx.EXPAND | wx.BOTTOM, border = pane_gap)
        detailsSizer.Add(swmmbuild_label, 0)
        detailsSizer.Add(self.swmmbuild_choice, 0, flag = wx.EXPAND | wx.BOTTOM, border = pane_gap)
        detailsSizer.Add(doc_label, 0)
        detailsSizer.Add(self.doc_text, 0, flag = wx.EXPAND | wx.BOTTOM, border = pane_gap)
        detailsSizer.Add(des_label, 0, flag = wx.LEFT)
        detailsSizer.Add(self.des_text, 1, flag = wx.EXPAND)
        detailsbtnSizer = wx.BoxSizer(wx.HORIZONTAL)
        detailsbtnSizer.Add(self.updatedetails_btn, 1)
        detailsbtnSizer.Add(self.resetdetails_btn, 1)
        detailsSizer.Add(detailsbtnSizer, 0, flag = wx.EXPAND | wx.TOP | wx.BOTTOM, border = 5)
        detailsPanel.SetSizer(detailsSizer)

        versionsplitter.SplitHorizontally(versionsPanel, detailsPanel)

        nb.AddPage(versionsplitter, 'Versions')
        versionsplitter.ResetSash()

        tagsplitter = ProportionalSplitter(nb, proportion = 0.9, minsize = 200, maxsize = 50)
        modeltagsPanel = wx.Panel(tagsplitter)
        modeltags_lbl = wx.StaticText(modeltagsPanel, label = 'Current Model Tags')
        modeltags_lbl.SetFont(self.headingfont)
        self.addtag_text = wx.TextCtrl(modeltagsPanel, value = '', size = (100, -1), style = wx.TE_PROCESS_ENTER)
        self.addtag_text.Disable()
        self.modeltags_lb = wx.CheckListBox(modeltagsPanel, size = (200, 300), choices = [])
        self.savemodeltags_btn = wx.Button(modeltagsPanel, label = 'Update')
        self.savemodeltags_btn.Disable()
        modeltagsPanel.Bind(wx.EVT_BUTTON, self.updateModelTags, self.savemodeltags_btn)
        self.resetmodeltags_btn = wx.Button(modeltagsPanel, label = 'Reset')
        self.resetmodeltags_btn.Disable()
        modeltagsPanel.Bind(wx.EVT_BUTTON, self.refreshModelTags, self.resetmodeltags_btn)
        def OnEnterTag(event):
            tags_csv = self.addtag_text.GetValue()
            tags_list = tags_csv.split(',')
            tags_list = [tag.strip() for tag in tags_list]
            has_spaces = any([re.search(' ', tag) for tag in tags_list])
            is_nonascii = any([ord(ch) >= 128 for ch in tags_csv])
            if has_spaces or is_nonascii:
                dlg = wx.MessageDialog(self, 'Tags should be ASCII characters separated by commas and without spaces.', 'Error', wx.OK)
                dlg.ShowModal()
            else:
                existing_tags = self.modeltags_lb.GetItems()
                new_tags = [tag for tag in tags_list if tag not in existing_tags]
                if len(new_tags) > 0:
                    checkedtags = list(self.modeltags_lb.GetCheckedStrings())
                    checkedtags = checkedtags + new_tags
                    self.modeltags_lb.AppendItems(new_tags)
                    self.modeltags_lb.SetCheckedStrings(checkedtags)
                    self.savemodeltags_btn.Enable()
                    self.resetmodeltags_btn.Enable()
                self.addtag_text.Clear()
        modeltagsPanel.Bind(wx.EVT_TEXT_ENTER, OnEnterTag, self.addtag_text)
        def OnModelTagCheck(event):
            self.savemodeltags_btn.Enable()
            self.resetmodeltags_btn.Enable()
        modeltagsPanel.Bind(wx.EVT_CHECKLISTBOX, OnModelTagCheck, self.modeltags_lb)

        modeltagsSizer = wx.BoxSizer(wx.VERTICAL)
        modeltagsSizer.Add(modeltags_lbl, 0, flag = wx.TOP | wx.BOTTOM, border = 5)
        modeltagsSizer.Add(self.addtag_text, 0, flag = wx.EXPAND | wx.BOTTOM, border = 3)
        modeltagsSizer.Add(self.modeltags_lb, 1, flag = wx.EXPAND)
        modeltags_btnSizer = wx.BoxSizer(wx.HORIZONTAL)
        modeltags_btnSizer.Add(self.savemodeltags_btn, 1, flag = wx.EXPAND)
        modeltags_btnSizer.Add(self.resetmodeltags_btn, 1, flag = wx.EXPAND)
        modeltagsSizer.Add(modeltags_btnSizer, 0, flag = wx.EXPAND | wx.TOP, border = 3)
        modeltagsPanel.SetSizer(modeltagsSizer)

        tagsPanel = wx.Panel(tagsplitter)
        tags_lbl = wx.StaticText(tagsPanel, label = 'Working Model Tags Filter')
        tags_lbl.SetFont(self.headingfont)
        self.tagsubset_rb = wx.RadioBox(tagsPanel, -1, "Tag operator", wx.DefaultPosition, (-1,-1), ['Or', 'And'], 2, wx.RA_SPECIFY_COLS)
        self.tagsubset_rb.Disable()
        self.tagsubset_rb.SetToolTip(wx.ToolTip("Choose the logical operator for tag subsetting"))
        self.alltagsbtn = wx.Button(tagsPanel, label='All')
        self.notagsbtn = wx.Button(tagsPanel, label='None')
        self.tags_button_list = [self.notagsbtn, self.alltagsbtn]
        for btn in self.tags_button_list:
            btn.Disable()
        self.tags_lb = wx.CheckListBox(tagsPanel, size=(160, 300), choices=[])
        applyfilter_btn = wx.Button(tagsPanel, label='Apply Filter')
        applyfilter_btn.Disable()
        def OnAllTags(event):
            self.tags_lb.SetCheckedStrings(self.tags_lb.GetItems())
            applyfilter_btn.Enable()
            self.refreshTagsButtons()
        tagsPanel.Bind(wx.EVT_BUTTON, OnAllTags, self.alltagsbtn)
        def OnNoTags(event):
            self.tags_lb.SetCheckedStrings([])
            applyfilter_btn.Enable()
            self.refreshTagsButtons()
        tagsPanel.Bind(wx.EVT_BUTTON, OnNoTags, self.notagsbtn)
        def OnCheckTag(event):
            self.refreshTagsButtons()
            applyfilter_btn.Enable()
        tagsPanel.Bind(wx.EVT_CHECKLISTBOX, OnCheckTag, self.tags_lb)
        def OnSetTagSubset(event):
            if len(self.tags_lb.GetItems()) > 0:
                applyfilter_btn.Enable()
        tagsPanel.Bind(wx.EVT_RADIOBOX, OnSetTagSubset, self.tagsubset_rb)
        def OnApplyFilter(event):
            self.setConfig(checked_tags = self.tags_lb.GetCheckedStrings(), uniontags = (self.tagsubset_rb.GetSelection() == 0))
            self.refresh()
            applyfilter_btn.Disable()
        tagsPanel.Bind(wx.EVT_BUTTON, OnApplyFilter, applyfilter_btn)
        tagSizer = wx.BoxSizer(wx.VERTICAL)
        tagSizer.Add(tags_lbl, 0, flag = wx.TOP | wx.BOTTOM, border = 5)
        tagSizer.Add(self.tagsubset_rb, 0, flag = wx.EXPAND | wx.BOTTOM, border = 3)
        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
        btnsizer.Add(self.alltagsbtn, 1)
        btnsizer.Add(self.notagsbtn, 1)
        tagSizer.Add(btnsizer, 0, wx.EXPAND | wx.ALL, 0)
        tagSizer.Add(self.tags_lb, 1, wx.EXPAND | wx.ALL, 0) 
        tagSizer.Add(applyfilter_btn, 0, wx.EXPAND | wx.TOP, 3)
        tagsPanel.SetSizer(tagSizer)
        tagsplitter.SplitHorizontally(modeltagsPanel, tagsPanel)

        nb.AddPage(tagsplitter, 'Tags')

        sidepanelSizer.Add(nb, 1, flag = wx.EXPAND)
        self.sidepanel.SetSizer(sidepanelSizer)

        self.mainsplitter.SplitVertically(self.modelsplitter, self.sidepanel)
        self.modelsplitter.SplitHorizontally(userlist_panel, excludedlist_panel)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.mainsplitter, 1, wx.EXPAND | wx.RIGHT | wx.TOP | wx.BOTTOM | wx.ALIGN_CENTER_VERTICAL, 3)
        mainpanel.SetSizer(sizer)
        mainpanel.Layout()
        self.Centre()

        self.sb = self.CreateStatusBar()
        self.sb.SetFieldsCount(2)
        self.SetStatusBar(self.sb)
        self.SetMinSize((100, 100))
        self.SetStatus('Disconnected', 1)

        self.Bind(wx.EVT_CLOSE, self.OnClose)

        self.getConfig()
        if self.getConfig('automaximize'):
            self.Maximize()
        if self.getConfig('autoconnect'):
            self.connect()

        self.Show()

    def OnToolClick(self, event):
        id = event.GetId()
        if id == self.TOOL_UPLOAD:
            self.uploadINP()
        elif id == self.TOOL_CASCADE:
            self.prepCascade()
        elif id == self.TOOL_DOWNLOAD:
            self.export()
        elif id == self.TOOL_SWMM:
            self.openswmm()
        elif id == self.TOOL_COMMIT:
            self.commit()

    def toggleBusy(self, busy):
        if busy:
            for id in self.tool_ids:
                self.tb.EnableTool(id, False)
            for btn in self.tags_button_list:
                btn.Disable()
        else:
            if self.isconnected:
                self.tb.EnableTool(self.TOOL_UPLOAD, True)
                self.refreshTagsButtons()
            if self.current_model is not None:
                for id in self.version_tool_ids:
                    self.tb.EnableTool(id, True)
            if self.file_menu.IsEnabled(self.MENU_COMMIT):
                self.tb.EnableTool(self.TOOL_COMMIT, True)
        self.usermodels_list.Enable(not busy)
        self.excludedmodels_list.Enable(not busy)
        self.sidepanel.Enable(not busy)

    def showError(self, msg):
        wxdialogs.ScrolledMessageDialog(self, msg = msg, caption = 'Error', style = wx.OK | wx.DEFAULT_DIALOG_STYLE).ShowModal()

    def Listen(self, f, kwargs, handler, handler_kwargs = {}):
        self.toggleBusy(True)

        try:
            result = f(**kwargs)
        except:
            exc = ''.join(traceback.format_exception(*sys.exc_info()))
            result = None
        else:
            exc = None

        self.toggleBusy(False)
        wx.CallAfter(handler, exc = exc, result = result, **handler_kwargs)

    def SetStatus(self, msg, field = 0, clear=False):
        if clear:
            self.sb.SetStatusText('', 0)
            self.sb.SetStatusText('', 1)
        self.sb.SetStatusText(msg, field)

    def getConfig(self, *args):
        if len(args) == 0:
            parser = ConfigParser.SafeConfigParser()
            parser.read(self.defaultcfg_path)
            self.defaultcfg = {}
            for key, value in parser.items('main'):
                self.defaultcfg[key] = eval(value)

            if not os.path.exists(self.usercfg_path):
                shutil.copy(self.defaultcfg_path, self.usercfg_path)

            parser = ConfigParser.SafeConfigParser()
            parser.read(self.usercfg_path)
            self.usercfg = {}
            for key, value in parser.items('main'):
                try:
                    self.usercfg[key] = eval(value)
                except:
                    if key in self.defaultcfg.keys():
                        self.usercfg[key] = self.defaultcfg[key]
                    else:
                        self.usercfg[key] = None
        else:
            settings = {}
            for arg in args:
                try:
                    val = self.usercfg[arg]
                except:
                    val = self.defaultcfg[arg]
                settings[arg] = val

            if len(settings) == 1:
                return settings.values()[0]
            else:
                return settings

    def setConfig(self, **kwargs):
        for key in kwargs.keys():
            value = kwargs[key]
            if isinstance(value, unicode):
                value = str(value)
            self.usercfg[key] = value

    def saveConfig(self):
        parser = ConfigParser.SafeConfigParser()
        parser.add_section('main')
        for key, value in self.usercfg.items():
            if isinstance(value, str) or isinstance(value, unicode):
                value = re.sub(r'\\*$', '', str(value))
                value = '"' + value + '"'
            parser.set('main', str(key), str(value))
        parser.set('main', 'automaximize', str(self.IsMaximized()))
        parser.set('main', 'autoconnect', str(self.isconnected))
        with open(self.usercfg_path, 'w') as f:
            parser.write(f)

    def config_connect(self):
        server = self.getConfig('server')
        login = self.getConfig('login')
        password = self.getConfig('password')

        cfgConnection = wx.Dialog(self, title = 'Connect', size = (300, 230), style = wx.DEFAULT_DIALOG_STYLE)

        txt_wh = (200, -1)
        lblserver = wx.StaticText(cfgConnection, label='Server :')
        if server is None:
            server = ''
        text_server = wx.TextCtrl(cfgConnection, value=server, size=txt_wh)

        winauth_cb = wx.CheckBox(cfgConnection, -1, 'Use Windows authentication')

        lbllogin = wx.StaticText(cfgConnection, label='Login :')
        text_login = wx.TextCtrl(cfgConnection, value = '', size=txt_wh)

        lblpassword = wx.StaticText(cfgConnection, label='Password :')
        text_password = wx.TextCtrl(cfgConnection, value = '', size=txt_wh, style = wx.TE_PASSWORD)

        if login is None:
            winauth_cb.SetValue(True)
            text_login.Disable()
            text_password.Disable()
        else:
            text_login.SetValue(login)
            if password is not None:
                text_password.SetValue(password)

        def OnWinAuth(event):
            if event.IsChecked():
                text_login.Disable()
                text_password.Disable()
            else:
                text_login.Enable()
                text_password.Enable()

        cfgConnection.Bind(wx.EVT_CHECKBOX, OnWinAuth, winauth_cb)

        line = wx.StaticLine(cfgConnection, -1, size=(20, -1), style=wx.LI_HORIZONTAL)
        connectbtn = wx.Button(cfgConnection, wx.ID_OK, label='Connect') 
        cancelbtn = wx.Button(cfgConnection, wx.ID_CANCEL, label='Cancel')

        grid = wx.GridBagSizer(hgap=10, vgap=10)
        grid.Add(lblserver, pos = (0,0), flag = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)
        grid.Add(text_server, pos = (0,1))
        grid.Add(winauth_cb, pos = (1,1), flag = wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL)
        grid.Add(lbllogin, pos = (2,0), flag = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)
        grid.Add(text_login, pos = (2,1))
        grid.Add(lblpassword, pos = (3,0), flag = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)
        grid.Add(text_password, pos = (3,1))
    
        vSizer = wx.BoxSizer(wx.VERTICAL)
        vSizer.Add(grid, 0, wx.ALL, 10)
        vSizer.Add(line, 0, wx.ALL | wx.GROW | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER_HORIZONTAL, 5)

        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
        btnsizer.Add(connectbtn, 1)
        btnsizer.Add(cancelbtn, 1)
        vSizer.Add(btnsizer, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT, 5)

        cfgConnection.SetSizer(vSizer)
        vSizer.Fit(cfgConnection)
        cfgConnection.Centre()

        if cfgConnection.ShowModal() == wx.ID_OK:
            server = text_server.GetValue()
            if text_login.IsEnabled():
                login = text_login.GetValue()
                password = text_password.GetValue()
            else:
                login = None
                password = None

            self.setConfig(server = server,
                           login = login,
                           password = password)

            self.connect()

        cfgConnection.Destroy()

    def OnSetTagSubset(self, event):
        self.setConfig(uniontags = event.GetInt() == 0)
        self.refreshListData()

    def refreshTags(self):
        try:
            alltags = self.swmmrh.getTags()
        except:
            self.showError("Couldn't fetch model tags")
            return None
        else:
            self.tags_lb.Clear()
            self.tags_lb.AppendItems(alltags)
            checkedtags = self.getConfig('checked_tags')
            checkedtags = [tag for tag in checkedtags if tag in alltags]
            self.tags_lb.SetCheckedStrings(checkedtags)
            self.tags_lb.Refresh()
            self.refreshTagsButtons()
            return checkedtags

    def refreshListData(self):
        self.OnModelDeselected()
        self.usermodels_list.DeleteAllItems()
        self.excludedmodels_list.DeleteAllItems()
        if len(self.allmodels) > 0:
            union_col = 7
            inter_col = 8
            use_union = self.getConfig('uniontags')
            for row in self.allmodels.values():
                if (use_union and row[union_col] == 1) or ((not use_union) and row[inter_col] == 1):
                    modellist = self.usermodels_list
                else:
                    modellist = self.excludedmodels_list

                index = modellist.InsertStringItem(0, row[0])
                for i in range(1, len(self.columndef)):
                    value = row[i]
                    if value is None:
                        value = ''
                    elif isinstance(value, datetime):
                        value = value.strftime('%m/%d/%Y %H:%M:%S')
                    elif not isinstance(value, str):
                        value = str(value)
                        
                    modellist.SetStringItem(index, i, value)

    def refresh(self):
        if self.isconnected:
            checkedtags = self.refreshTags()
            checked_csv = ','.join(checkedtags)
            threading.Thread(target = self.Listen, args = (self.swmmrh.getAllModels, {'tags_csv' : checked_csv}, self.OnRefreshComplete)).start()

    def OnRefreshComplete(self, exc, result):
        if exc:
            self.SetStatus('Failed to refresh the database')
            self.showError(exc)
        else:
            allmodels = result
            self.allmodels = dict([(i, row) for i, row in enumerate(allmodels)])
            self.refreshListData()
            self.usermodels_list.currentItem = 0

    def connect(self, cfg = False):
        if cfg or self.getConfig('server') is None:
            self.config_connect()
        else:
            self.SetStatus('Connecting...')
            threading.Thread(target = self.Listen, args = (swmmr.handle, self.getConfig('server', 'login', 'password'), self.OnConnectComplete)).start()

    def OnConnectComplete(self, exc, result):
        if exc:
            self.SetStatus('Attempt to connect failed.')
            self.isconnected = False
            self.showError(exc)
        else:
            self.swmmrh = result
            self.isconnected = True
            self.file_menu.Enable(self.MENU_DISCONNECT, True)
            self.file_menu.Enable(self.MENU_REFRESH, True)
            self.tb.EnableTool(self.TOOL_UPLOAD, True)
            self.SetStatus('Connected: ' + self.swmmrh.server + ' (' + self.swmmrh.login + ')', 1, clear=True)
            self.refresh()

    def isBusy(self):
        return threading.active_count() > 0

    def disconnect(self, closewindow=False):
        if self.isbusy:
            dlg = wx.MessageDialog(self, 'Disconnecting in the middle of a transaction will rollback to your last commit. Continue?', 
                                  'Disconnect', wx.YES_NO)
            choice = dlg.ShowModal()
            if choice == wx.ID_YES:
                threading.Thread(target = self.Listen, args = (self.swmmrh.rollback, {}, self.OnRollbackComplete, {'closewindow' : closewindow})).start()
        else:
            if self.updatedetails_btn.IsEnabled():
                dlg = wx.MessageDialog(self, 'Save changes to version information for ' + self.current_model + '?', 'Wait!', wx.YES_NO)
                if dlg.ShowModal() == wx.ID_YES:
                    update_kwargs = {'version_id'   : self.currentmodel_versiondata[self.currentversion_ix][self.IX_VERSIONID],
                                     'name'         : self.version_text.GetValue(), 
                                     'swmmbuild'    : self.swmmbuild_choice.GetStringSelection(),
                                     'docpath'      : self.doc_text.GetValue(),
                                     'desc'         : self.des_text.GetValue()}
                    try:
                        self.swmmrh.updateModelVersion(**update_kwargs)
                    except:
                        exc_formatted = ''.join(traceback.format_exception(*sys.exc_info()))
                        self.showError(exc_formatted)
                    else:
                        self.enableCommit(True)

                self.updatedetails_btn.Disable()
                self.resetdetails_btn.Disable()

            if self.savemodeltags_btn.IsEnabled():
                dlg = wx.MessageDialog(self, 'Save changes to tags model tags?', 'Wait!', wx.YES_NO)
                if dlg.ShowModal() == wx.ID_YES:
                    checked_modeltags = self.modeltags_lb.GetCheckedStrings()
                    checkedtags_csv = ','.join(checked_modeltags)
                    try:
                        self.swmmrh.setModelTags(tags_csv = checkedtags_csv, model = self.current_model)
                    except:
                        exc_formatted = ''.join(traceback.format_exception(*sys.exc_info()))
                        self.showError(exc_formatted)
                    else:
                        self.enableCommit(True)
                self.savemodeltags_btn.Disable()
                self.resetmodeltags_btn.Disable()

            if self.hasUncommittedChanges():
                dlg = wx.MessageDialog(self, 'Commit before disconnecting?', 'Uncommitted changes',  wx.YES_NO | wx.CANCEL)
                choice = dlg.ShowModal()
                if choice == wx.ID_NO or choice == wx.ID_YES:
                    if choice == wx.ID_YES:
                        self.commit(disconnect = True, closewindow = closewindow)
                    else:
                        threading.Thread(target = self.Listen, 
                                         args = (self.swmmrh.rollback, {}, self.OnRollbackComplete, {'closewindow' : closewindow})).start()
            else:
                threading.Thread(target = self.Listen, args = (self.swmmrh.close, {}, self.OnDisconnectComplete, {'closewindow' : closewindow})).start()

    def OnDisconnectComplete(self, exc, result, closewindow):
        if exc:
            self.showError(exc)

        self.usermodels_list.DeleteAllItems()
        self.excludedmodels_list.DeleteAllItems()
        self.tags_lb.Clear()
        self.modeltags_lb.Clear()
        self.tagsubset_rb.Disable()
        self.versions_lb.Clear()
        self.version_text.Clear()
        self.version_text.Disable()
        self.swmmbuild_choice.Clear()
        self.swmmbuild_choice.Disable()
        self.doc_text.Clear()
        self.doc_text.Disable()
        self.des_text.Clear()
        self.des_text.Disable()
        self.updatedetails_btn.Disable()
        self.resetdetails_btn.Disable()
        self.file_menu.Enable(self.MENU_DISCONNECT, False)
        self.file_menu.Enable(self.MENU_REFRESH, False)
        for id in self.tool_ids:
            self.tb.EnableTool(id, False)
        for btn in self.tags_button_list:
            btn.Disable()
        self.isconnected = False
        self.swmmrh = None
        self.SetStatus('Disconnected', 1, clear=False)
        if closewindow:
            self.Destroy()

    def cfgSWMMPaths(self):
        dlg_args = self.getConfig('swmmgui', 'swmm')
        dlg = SWMMConfigDialog(self, **dlg_args)
        if dlg.ShowModal() == wx.ID_OK:
            swmmgui = dlg.getSWMMGUI()
            swmm = dlg.getSWMM()
            self.setConfig(swmmgui = swmmgui, swmm = swmm)
        dlg.Destroy()

    def build(self, admin_password = ''):
        cfgBuild = wx.Dialog(self, title ='Build database', size=(300, 300), style = wx.DEFAULT_DIALOG_STYLE)

        txt_wh = (200, -1)
        lblserver = wx.StaticText(cfgBuild, label='Server :')
        build_server = self.getConfig('build_server')
        if build_server is None:
            build_server = ''
        text_server = wx.TextCtrl(cfgBuild, value=build_server, size=txt_wh)

        lbllogin = wx.StaticText(cfgBuild, label='Admin login:')
        admin_login = self.getConfig('admin_login')
        if admin_login is None:
            admin_login = ''
        text_login = wx.TextCtrl(cfgBuild, value=admin_login, size=txt_wh)

        lblpassword = wx.StaticText(cfgBuild, label='Admin password:')
        text_password = wx.TextCtrl(cfgBuild, value=admin_password, size=txt_wh, style = wx.TE_PASSWORD)

        line = wx.StaticLine(cfgBuild, -1, size=(20, -1), style=wx.LI_HORIZONTAL)

        buildbtn = wx.Button(cfgBuild, wx.ID_OK, label='Build') 
        cancelbtn = wx.Button(cfgBuild, wx.ID_CANCEL, label='Cancel') 

        grid = wx.GridBagSizer(hgap=10, vgap=10)
        grid.Add(lblserver, pos=(0,0), flag = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)
        grid.Add(text_server, pos=(0,1))
        grid.Add(lbllogin, pos=(1,0), flag = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)
        grid.Add(text_login, pos=(1,1))
        grid.Add(lblpassword, pos=(2,0), flag = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)
        grid.Add(text_password, pos=(2,1))

        vSizer = wx.BoxSizer(wx.VERTICAL)
        vSizer.Add(grid, 0, wx.ALL, 10)
        vSizer.Add(line, 0, wx.ALL | wx.GROW | wx.ALIGN_CENTER_VERTICAL, 5)
        
        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
        btnsizer.Add(buildbtn, 0)
        btnsizer.Add(cancelbtn, 0)
        vSizer.Add(btnsizer, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT, 5)

        cfgBuild.SetSizer(vSizer)
        vSizer.Fit(cfgBuild)

        if cfgBuild.ShowModal() == wx.ID_OK:
            build_server = text_server.GetValue()
            admin_login = text_login.GetValue()
            admin_password = text_password.GetValue()
            self.setConfig(build_server = build_server, admin_login = admin_login)
            okToBuild = True
            if self.isconnected: 
                if self.swmmrh.server == build_server:
                    exc_formatted = ''.join(traceback.format_exception(*sys.exc_info()))
                    self.showError(exc_formatted)
                    okToBuild = False
            if okToBuild:
                self.SetStatus('Setting up SWMMR on ' + build_server + '...')
                build_kwargs = {'server'           : build_server, 
                               'admin_login'      : admin_login, 
                               'admin_password'   : admin_password}
                threading.Thread(target = self.Listen, 
                                 args = (swmmr.build, build_kwargs, self.OnBuildComplete, {'admin_password' : admin_password})).start()

    def OnBuildComplete(self, exc, result, admin_password):
        if exc:
            self.showError(exc)
            self.SetStatus('Setup encountered an error.')
            self.build(admin_password)
        else:
            self.SetStatus('Setup complete')
            if not self.isconnected:
                self.setConfig(server = self.getConfig('build_server'))
                self.connect(cfg = True)

    def swmmpath(self):
        cfgSWMMPath = wx.Dialog(self, title = 'Set SWMM GUI path', style = wx.DEFAULT_DIALOG_STYLE)
        swmmgui = self.getConfig('swmmgui')
        if not os.path.exists(swmmgui):
            swmmgui = ''

        lblswmmgui = wx.StaticText(cfgSWMMPath, label='SWMM GUI Path :')
        text_swmmgui = wx.TextCtrl(cfgSWMMPath, value=swmmgui, size=(350, -1), validator = SWMMPathValidator())

        line = wx.StaticLine(cfgSWMMPath, -1, size=(20, -1), style=wx.LI_HORIZONTAL)
        self.savebtn = wx.Button(cfgSWMMPath, wx.ID_OK, label='Set') 
        self.cancelbtn = wx.Button(cfgSWMMPath, wx.ID_CANCEL, label='Cancel') 

        grid = wx.GridBagSizer(hgap=10, vgap=10)
        grid.Add(lblswmmgui, pos=(0,0), flag = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)
        grid.Add(text_swmmgui, pos=(0,1))
        vSizer = wx.BoxSizer(wx.VERTICAL)
        vSizer.Add(grid, 0, wx.ALL,  10)
        vSizer.Add(line, 0, wx.ALL | wx.GROW | wx.ALIGN_CENTER_VERTICAL, 5)
        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
        btnsizer.Add(self.savebtn, 1)
        btnsizer.Add(self.cancelbtn, 1)
        vSizer.Add(btnsizer, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT, 5)
        cfgSWMMPath.SetSizer(vSizer)
        vSizer.Fit(cfgSWMMPath)
        cfgSWMMPath.CenterOnScreen()

        if cfgSWMMPath.ShowModal() == wx.ID_OK:
            self.setConfig(swmmgui = text_swmmgui.GetValue())

    def OnMenu(self, event):
        menu_id = event.GetId()
        if menu_id == self.MENU_CONNECT:
            self.connect(cfg = True)
        elif menu_id == self.MENU_DISCONNECT:
            self.disconnect()
        elif menu_id == self.MENU_REFRESH:
            self.refresh()
        elif menu_id == self.MENU_BUILD:
            self.build()
        elif menu_id == self.MENU_SWMMPATHS:
            self.cfgSWMMPaths()
        elif menu_id == self.MENU_ABOUT:
            dbversion = swmmr.version()

            info = wx.AboutDialogInfo()
            info.Name = 'SWMMR Interface'
            info.Version = _version + ' (SWMMR ' + dbversion + ')'
            info.Copyright = "(C) CDMSmith / Philadelphia Water Dept. Office of Watersheds"
            info.Description = 'Licensed under GNU GPL v3'
            info.WebSite = 'http://swmmr.googlecode.com'
            wx.AboutBox(info)
        elif menu_id == self.MENU_CLOSE:
            self.OnClose()
        elif menu_id == self.MENU_SWMMPATH:
            self.swmmpath()
        elif menu_id == self.MENU_COMMIT:
            self.commit()

    def refreshTagsButtons(self):
        num_checked = len(self.tags_lb.GetChecked())
        num_tags = len(self.tags_lb.GetItems())
        if num_tags == 0:
            self.alltagsbtn.Disable()
            self.notagsbtn.Disable()
        elif num_tags == num_checked:
            self.alltagsbtn.Disable()
            self.notagsbtn.Enable()
        elif num_checked == 0:
            self.alltagsbtn.Enable()
            self.notagsbtn.Disable()
        else:
            self.alltagsbtn.Enable()
            self.notagsbtn.Enable()

        if not self.tagsubset_rb.IsEnabled():
            self.tagsubset_rb.Enable()
            if self.getConfig('uniontags') == True:
                self.tagsubset_rb.SetSelection(0)
            else:
                self.tagsubset_rb.SetSelection(1)

    def GetListCtrl(self):
        # used by ColumnSorterMixin
        return self.usermodels_list

    def OnResize(self, event):
        pass

    def OnClose(self, event = None):
        self.saveConfig() # disconnect saves config
        if self.isconnected:
            self.disconnect(closewindow = True)
        else:
            self.Destroy()

    def OnRollbackComplete(self, exc, result, closewindow):
        if exc:
            self.showError(exc)
            self.SetStatus('Failed to rollback session')
        else:
            self.SetStatus('Rollback complete')
            
        self.enableCommit(False)
        self.disconnect(closewindow = closewindow)

    def hasUncommittedChanges(self):
        return self.file_menu.IsEnabled(self.MENU_COMMIT)

    def enableCommit(self, toggle):
        self.file_menu.Enable(self.MENU_COMMIT, toggle)
        self.tb.EnableTool(self.TOOL_COMMIT, toggle)

    def commit(self, disconnect = False, closewindow = False):
        threading.Thread(target = self.Listen, 
                         args = (self.swmmrh.commit, {}, self.OnCommitComplete, {'disconnect' : disconnect, 'closewindow' : closewindow})).start()

    def OnCommitComplete(self, exc, result, disconnect, closewindow):
        if exc:
            self.showError(exc)
            self.SetStatus('Attempt to save changes failed.')
            disconnect = True
        else:
            self.SetStatus("Changes saved.")
        self.enableCommit(False)
        self.file_menu.Enable(self.MENU_COMMIT, False)
        self.tb.EnableTool(self.TOOL_COMMIT, False)
        if disconnect:
            self.disconnect(closewindow = closewindow)

    def uploadINP(self, event = None):
        cfgUpload = wx.Dialog(self, title = 'Import a new model or update an existing one', style = wx.DEFAULT_DIALOG_STYLE)

        radio_new = wx.RadioButton(cfgUpload, -1, 'Create new model')
        radio_new.SetValue(True)
        def OnRadioNew(event):
            text_model.Enable()
            text_model.Clear()
        cfgUpload.Bind(wx.EVT_RADIOBUTTON, OnRadioNew, radio_new)
        radio_existing = wx.RadioButton(cfgUpload, -1, 'Update existing model')
        active_modelnames = self.swmmrh.getModelNames(active_only = True)
        if len(active_modelnames) == 0:
            radio_existing.Disable()
        all_modelnames = self.swmmrh.getModelNames()

        txt_wh = (390, -1)
        lblmodel = wx.StaticText(cfgUpload, -1, label='Model Name:')
        text_model = wx.TextCtrl(cfgUpload, size=txt_wh)
        lblinp = wx.StaticText(cfgUpload, label='INP File:')
        text_inp = wx.TextCtrl(cfgUpload, size=txt_wh, validator=INPPathValidator())
        btnsize = (85, -1)
        files_btn = wx.Button(cfgUpload, label="Browse Files", size= btnsize)
        def OnBrowseFiles(event):
            wildcard = "SWMM INP (*.inp)|*.inp|" \
                       "All files (*.*)|*.*"
            dlg = wx.FileDialog(cfgUpload, message="Choose a file", defaultDir = self.getConfig('defaultdir'), defaultFile = "", 
                    wildcard = wildcard, style = wx.OPEN)
            dlg.CenterOnScreen()
            if dlg.ShowModal() == wx.ID_OK:
                path = dlg.GetPaths()[0]
                text_inp.Clear()
                text_inp.AppendText(path)
                self.setConfig(defaultdir = os.path.dirname(path))
                if text_model.GetValue() == '':
                    fname = os.path.basename(path)
                    pat = re.compile('.inp$', re.IGNORECASE)
                    model = re.sub(pat, '', fname)
                    text_model.Clear()
                    text_model.AppendText(model)
        cfgUpload.Bind(wx.EVT_BUTTON, OnBrowseFiles, files_btn)

        lblswmmbuild = wx.StaticText(cfgUpload, -1, label = 'SWMM Build:')
        swmmbuilds = self.swmmrh.getSupportedSWMMBuilds()
        choice_swmmbuild = wx.Choice(cfgUpload, -1, size = (100, -1), choices = swmmbuilds)
        lbloptional = wx.StaticText(cfgUpload, -1, label = 'Optional')
        lbloptional.SetFont(self.headingfont)

        lbltags = wx.StaticText(cfgUpload, label='Add Model Tags:')
        text_tags = wx.TextCtrl(cfgUpload, size = txt_wh, validator = TagValidator())
        self.uploadaddtag_btn = wx.Button(cfgUpload, -1, label = 'Tag List', size= btnsize)
        alltags = self.swmmrh.getTags()
        if len(alltags) == 0:
            self.uploadaddtag_btn.Disable()
        def OnAddTag(event):
            dlg = wx.SingleChoiceDialog(cfgUpload, "Select the tag you want to add", "Add tag", alltags)
            if dlg.ShowModal() == wx.ID_OK:
                selection = alltags[dlg.GetSelection()]
                tags_csv = text_tags.GetValue()
                tags_csv = tags_csv.strip()
                if len(tags_csv) > 0 and not re.search(',$', tags_csv):
                    tags_csv = tags_csv + ', '
                tags_csv = tags_csv + ', ' + selection + ', '
                text_tags.Clear()
                text_tags.AppendText(tags_csv)
        cfgUpload.Bind(wx.EVT_BUTTON, OnAddTag, self.uploadaddtag_btn)

        lblversion = wx.StaticText(cfgUpload, -1, label = 'Version Name:')
        text_version = wx.TextCtrl(cfgUpload, value = '', size = txt_wh, validator = VersionValidator())

        lbldoc = wx.StaticText(cfgUpload, -1, label = 'Path to documentation:')
        text_doc = wx.TextCtrl(cfgUpload, value = '', size = txt_wh)

        lbldesc = wx.StaticText(cfgUpload, -1, label = 'Description:')
        text_desc = wx.TextCtrl(cfgUpload, value = '', size = (txt_wh[0], 120), style = wx.TE_MULTILINE)
        
        def OnRadioExisting(event):
            if text_model.GetValue() not in active_modelnames:
                dlg = wx.SingleChoiceDialog(cfgUpload, message = 'Select a model', caption = 'Active models', 
                                            choices = active_modelnames, style = wx.CHOICEDLG_STYLE) 
                dlg.CenterOnScreen()
                if dlg.ShowModal() == wx.ID_OK:
                    model = dlg.GetStringSelection()
                    latest_version_data = self.swmmrh.getModelVersion(model)
                    text_model.Clear()
                    text_model.AppendText(model)
                    text_model.Disable()
                    choice_swmmbuild.SetStringSelection(latest_version_data[self.IX_BUILD])
                    name = latest_version_data[self.IX_VNAME]
                    name = name if name is not None else ''
                    text_version.Clear()
                    text_version.AppendText(name)
                    doc = latest_version_data[self.IX_DOC]
                    doc = doc if doc is not None else ''
                    text_doc.Clear()
                    text_doc.AppendText(doc)
                    desc = latest_version_data[self.IX_DESC]
                    desc = desc if desc is not None else ''
                    text_desc.Clear()
                    text_desc.AppendText(desc)
                else:
                    radio_existing.SetValue(False)
                    radio_new.SetValue(True)
                    text_model.Enable()
            else:
                text_model.Disable()
                
        cfgUpload.Bind(wx.EVT_RADIOBUTTON, OnRadioExisting, radio_existing) 

        class ModelNameValidator(wx.PyValidator):
            def __init__(self):
                wx.PyValidator.__init__(self)

            def Clone(self):
                return ModelNameValidator()

            def Validate(self, win):
                textCtrl = self.GetWindow()
                modelname = textCtrl.GetValue()
                masterframe = textCtrl.GetGrandParent()

                if len(modelname) == 0:
                    wx.MessageBox("Select or enter a model name.", "Error")
                    return False
                elif modelname in all_modelnames and modelname not in active_modelnames:
                    wx.MessageBox("This model has been deactivated.", "Error")
                    return False
                elif radio_new.GetValue() and modelname in active_modelnames:
                    dlg = wx.MessageDialog(cfgUpload, "A model with that name already exists. Do you want to update it?", 
                            "Confirm model update", wx.YES_NO)
                    if dlg.ShowModal() == wx.ID_YES:
                        latest_version_data = masterframe.swmmrh.getModelVersion(modelname)
                        name = latest_version_data[masterframe.IX_VNAME]
                        name = name if name is not None else ''
                        code.interact(local=locals())
                        if not text_version.GetValue():
                            text_version.Clear()
                            text_version.AppendText(name)
                        if choice_swmmbuild.GetSelection() == wx.NOT_FOUND:
                            choice_swmmbuild.SetStringSelection(latest_version_data[masterframe.IX_BUILD])
                        if not text_doc.GetValue():
                            doc = latest_version_data[masterframe.IX_DOC]
                            doc = doc if doc is not None else ''
                            text_doc.Clear()
                            text_doc.AppendText(doc)
                        if not text_desc.GetValue():
                            desc = latest_version_data[masterframe.IX_DESC]
                            desc = desc if desc is not None else ''
                            text_desc.Clear()
                            text_desc.AppendText(desc)
                        return True
                    else:
                        return False
                else:
                    return True

            def TransferToWindow(self):
                return True

            def TransferFromWindow(self):
                return True

        text_model.SetValidator(ModelNameValidator())

        line = wx.StaticLine(cfgUpload, -1, size=(20, -1), style = wx.LI_HORIZONTAL)

        loadbtn = wx.Button(cfgUpload, wx.ID_OK, label="OK")
        cancelbtn = wx.Button(cfgUpload, wx.ID_CANCEL, label="Cancel")

        vSizer = wx.BoxSizer(wx.VERTICAL)
        grid = wx.GridBagSizer(hgap=5, vgap=5)
        radioSizer = wx.BoxSizer(wx.HORIZONTAL)
        radioSizer.Add(radio_new, 0, flag = wx.RIGHT, border = 8)
        radioSizer.Add(radio_existing, 0)
        grid.Add(radioSizer, pos = (0,1))
        grid.Add(lblmodel, pos=(1, 0), flag=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)
        grid.Add(text_model, pos=(1, 1))
        grid.Add(lblinp, pos=(2,0), flag=wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)
        grid.Add(text_inp, pos=(2,1))
        grid.Add(files_btn, pos=(2,2), flag=wx.ALIGN_CENTER)
        grid.Add(lblswmmbuild, pos = (3,0), flag = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)
        grid.Add(choice_swmmbuild, pos = (3,1))
        grid.Add((8,8), pos = (4,0))
        grid.Add(lbloptional, pos = (5, 1), flag = wx.ALIGN_LEFT | wx.ALIGN_CENTER_VERTICAL)
        grid.Add(lbltags, pos = (6,0), flag = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)
        grid.Add(text_tags, pos = (6,1))
        grid.Add(self.uploadaddtag_btn, pos = (6,2))
        grid.Add(lblversion, pos = (7, 0), flag = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)
        grid.Add(text_version, pos = (7,1), span=(1,3))
        grid.Add(lbldoc, pos = (8,0), flag = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)
        grid.Add(text_doc, pos = (8,1), span=(1,3))
        grid.Add(lbldesc, pos = (9,0), flag = wx.ALIGN_RIGHT | wx.ALIGN_CENTER_VERTICAL)
        grid.Add(text_desc, pos = (9,1), span = (3,3))
        vSizer.Add(grid, 0, flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER_HORIZONTAL, border = 10)

        vSizer.Add(line, 0, wx.GROW | wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL | wx.TOP, 5)
        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
        btnsizer.Add(loadbtn, 1)
        btnsizer.Add(cancelbtn, 1)
        vSizer.Add(btnsizer, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.ALL, 5)

        cfgUpload.SetSizer(vSizer)
        vSizer.Fit(cfgUpload)
        cfgUpload.CenterOnScreen()

        if cfgUpload.ShowModal() == wx.ID_OK:
            model = text_model.GetValue()
            tags_csv = text_tags.GetValue()
            tags_csv = re.sub(' ', '', tags_csv)

            update_kwargs = {'model'        : model,
                             'inp_path'     : text_inp.GetValue(),
                             'swmmbuild'    : choice_swmmbuild.GetStringSelection(),
                             'version'      : text_version.GetValue(),
                             'docpath'      : text_doc.GetValue(),
                             'desc'         : text_desc.GetValue(),
                             'tags_csv'     : tags_csv}

            self.SetStatus('Uploading "' + os.path.basename(update_kwargs['inp_path']) +  '"...')
            threading.Thread(target = self.Listen, args = (self.swmmrh.importINP, update_kwargs, self.OnUploadINPComplete)).start()
        
        cfgUpload.Destroy()

    def OnUploadINPComplete(self, exc, result):
        if exc:
            self.SetStatus('Upload failed.')
            if self.hasUncommittedChanges():
                self.enableCommit(True)
            self.showError(exc)
        else:
            self.SetStatus('Upload complete.')
            self.enableCommit(True)
            self.refresh()

    def prepCascade(self):
        dlg = wx.Dialog(self, -1, 'Cascade model tag filter', size = (210, 500), style = wx.DEFAULT_DIALOG_STYLE) 
        filter_rb = wx.RadioBox(dlg, -1, "Consider which models?", wx.DefaultPosition, (-1,-1), 
                ['Consider all models', 'Filter considered models by tag'], 1, wx.RA_SPECIFY_COLS)
         
        line1 = wx.StaticLine(dlg, -1, size = (20, -1), style = wx.LI_HORIZONTAL) 
        tagsubset_rb = wx.RadioBox(dlg, -1, "Tag operator", wx.DefaultPosition, (-1,-1), ['Or', 'And'], 2, wx.RA_SPECIFY_COLS)
        tagsubset_rb.Disable()
        tagsubset_rb.SetToolTip(wx.ToolTip("Choose the logical operator for tag subsetting"))
        alltagsbtn = wx.Button(dlg, label='All')
        alltagsbtn.Disable()
        notagsbtn = wx.Button(dlg, label='None')
        notagsbtn.Disable()
        alltags = self.swmmrh.getTags()
        tags_lb = wx.CheckListBox(dlg, size=(160, 300), choices=alltags)
        checkedtags = self.tags_lb.GetCheckedStrings()
        checkedtags = [tag for tag in checkedtags if tag in alltags]
        tags_lb.SetCheckedStrings(checkedtags)
        tags_lb.Disable()
        checkedtags = self.getConfig('checked_tags')
        checkedtags = [tag for tag in checkedtags if tag in alltags]
        self.tags_lb.SetCheckedStrings(checkedtags)
        self.tags_lb.Refresh()
        def refreshTagsButtons():
            num_checked = len(tags_lb.GetChecked())
            num_tags = len(tags_lb.GetItems())
            if num_tags == 0:
                alltagsbtn.Disable()
                notagsbtn.Disable()
            elif num_tags == num_checked:
                alltagsbtn.Disable()
                notagsbtn.Enable()
            elif num_checked == 0:
                alltagsbtn.Enable()
                notagsbtn.Disable()
            else:
                alltagsbtn.Enable()
                notagsbtn.Enable()
        def OnAllTags(event):
            tags_lb.SetCheckedStrings(tags_lb.GetItems())
            refreshTagsButtons()
        dlg.Bind(wx.EVT_BUTTON, OnAllTags, alltagsbtn)
        def OnNoTags(event):
            tags_lb.SetCheckedStrings([])
            refreshTagsButtons()
        dlg.Bind(wx.EVT_BUTTON, OnNoTags, notagsbtn)
        def OnCheckTag(event):
            refreshTagsButtons()
        dlg.Bind(wx.EVT_CHECKLISTBOX, OnCheckTag, tags_lb)

        def OnFilterRadio(event):
            if event.GetInt() == 0:
                tagsubset_rb.Disable()
                alltagsbtn.Disable()
                notagsbtn.Disable()
                tags_lb.Disable()
            else:
                tagsubset_rb.Enable()
                alltagsbtn.Enable()
                notagsbtn.Enable()
                tags_lb.Enable()
                refreshTagsButtons()
        dlg.Bind(wx.EVT_RADIOBOX, OnFilterRadio, filter_rb)
    
        line2 = wx.StaticLine(dlg, -1, size = (20, -1), style = wx.LI_HORIZONTAL) 
        continuebtn = wx.Button(dlg, wx.ID_OK, label = 'Continue')
        cancelbtn = wx.Button(dlg, wx.ID_CANCEL, label = 'Cancel')

        tagSizer = wx.BoxSizer(wx.VERTICAL)
        tagSizer.Add(filter_rb, 0, flag = wx.TOP | wx.LEFT | wx.RIGHT | wx.EXPAND, border = 10)
        tagSizer.Add(line1, 0, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER_HORIZONTAL, 5)
        tagSizer.Add(tagsubset_rb, 0, wx.LEFT | wx.RIGHT | wx.EXPAND, border = 10)
        btnsizer = wx.BoxSizer(wx.HORIZONTAL)
        btnsizer.Add(alltagsbtn, 1, flag = wx.TOP | wx.BOTTOM, border = 5)
        btnsizer.Add(notagsbtn, 1, flag = wx.TOP | wx.BOTTOM, border = 5)
        tagSizer.Add(btnsizer, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, 10)
        tagSizer.Add(tags_lb, 1, wx.EXPAND | wx.LEFT | wx.RIGHT, 10)
        tagSizer.Add(line2, 0, wx.ALL | wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER_HORIZONTAL, 5)
        btnsizer2 = wx.BoxSizer(wx.HORIZONTAL)
        btnsizer2.Add(continuebtn, 1)
        btnsizer2.Add(cancelbtn, 1)
        tagSizer.Add(btnsizer2, 0, wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_RIGHT, 10)
        dlg.SetSizer(tagSizer)
        if dlg.ShowModal() == wx.ID_OK:
            if filter_rb.GetSelection() == 0:
                tags_csv = None
                filterwithand = 1
            else:
                tags = tags_lb.GetCheckedStrings()
                tags_csv = ','.join(tags)
                filterwithand = 1 if tagsubset_rb.GetSelection() == 1 else 0
            
            self.SetStatus('Preparing to cascade ' + self.current_model + '...')
            threading.Thread(target = self.Listen, 
                             args = (self.swmmrh.prepCascade, {'model' : self.current_model, 'tags_csv' : tags_csv, 'filter_with_and' : filterwithand}, 
                                     self.OnPrepCascadeComplete, {'model' : self.current_model})).start()

        dlg.Destroy()

    def OnPrepCascadeComplete(self, exc, result, model):
        if exc:
            self.SetStatus('Attempt to prepare cascade failed.')
            self.showError(exc)
        else:
            self.SetStatus('Ready to cascade.')
            self.commitCascade(model)

    def commitCascade(self, model):
        cascadeDlg = CascadeDialog(self, self.swmmrh, title = 'Cascade model elements.')
        if cascadeDlg.ShowModal() == wx.ID_OK:
            threading.Thread(target = self.Listen, args = (self.swmmrh.CME_Commit, {'model' : model}, self.OnCommitCascadeComplete)).start()
        else:
            self.SetStatus('Cascade cancelled.')
            for id in self.version_tool_ids:
                self.tb.EnableTool(id, True)

    def OnCommitCascadeComplete(self, exc, result):
        if exc:
            self.SetStatus('Attempt to commit cascade failed.')
            self.showError(exc)
        else:
            num_committed = result
            self.SetStatus(str(num_committed) + ' elements written.')
            if num_committed > 0 or self.hasUncommittedChanges():
                self.enableCommit(True)
            self.refresh()

    def refreshModelTags(self, event = None):
        modeltags = self.swmmrh.getModelTags(self.current_model)
        self.modeltags_lb.SetItems(modeltags)
        self.modeltags_lb.SetCheckedStrings(modeltags)
        self.resetmodeltags_btn.Disable()
        self.savemodeltags_btn.Disable()
        
    def refreshModelVersions(self):
        self.currentmodel_versiondata = self.swmmrh.getModelVersions(self.current_model)
        version_labels = [[row[self.IX_TS].strftime('%m/%d/%y'), row[self.IX_VNAME] if row[self.IX_VNAME] != '' else '(unnamed version)']  \
                           for row in self.currentmodel_versiondata]
        version_labels = [' : '.join([row[0], row[1]]) for row in version_labels]
        self.versions_lb.SetItems(version_labels)
        self.versions_lb.SetSelection(0)
        self.currentversion_ix = 0
        self.fillVersionDetails()

    def fillVersionDetails(self, event = None):
        ix = self.currentversion_ix
        version_name = self.currentmodel_versiondata[ix][self.IX_VNAME]
        version_name = version_name if version_name is not None else ''
        self.version_text.SetValue(version_name)
        self.version_text.Enable()
        swmmbuilds = self.swmmrh.getSupportedSWMMBuilds()
        self.swmmbuild_choice.SetItems(swmmbuilds)
        self.swmmbuild_choice.SetSelection(swmmbuilds.index(self.currentmodel_versiondata[ix][self.IX_BUILD]))
        self.swmmbuild_choice.Enable()
        doc = self.currentmodel_versiondata[ix][self.IX_DOC]
        doc = doc if doc is not None else ''
        self.doc_text.SetValue(doc)
        self.doc_text.Enable()
        des = self.currentmodel_versiondata[ix][self.IX_DESC]
        des = des if des is not None else ''
        self.des_text.SetValue(des)
        self.des_text.Enable()

        self.updatedetails_btn.Disable()
        self.resetdetails_btn.Disable()
        for id in self.version_tool_ids:
            self.tb.EnableTool(id, True)

    def updateModelVersion(self, event=None):
        update_kwargs = {'version_id'   : self.currentmodel_versiondata[self.currentversion_ix][self.IX_VERSIONID],
                         'name'         : self.version_text.GetValue(), 
                         'swmmbuild'    : self.swmmbuild_choice.GetStringSelection(),
                         'docpath'      : self.doc_text.GetValue(),
                         'desc'         : self.des_text.GetValue()}

        threading.Thread(target = self.Listen, args = (self.swmmrh.updateModelVersion, update_kwargs, self.OnSetModelVersionComplete)).start()

    def OnSetModelVersionComplete(self, exc, result):
        if exc:
            self.SetStatus('Attempt to update model version information failed.')
            self.showError(exc)
        else:
            self.SetStatus('Model version information updated.')
            self.enableCommit(True)
            if self.usermodels_list.GetSelectedItemCount() != 0:
                self.refreshModelVersions()
                for id in self.version_tool_ids:
                    self.tb.EnableTool(id, True)
            self.updatedetails_btn.Disable()
            self.resetdetails_btn.Disable()
            self.tb.EnableTool(self.TOOL_COMMIT, True)

    def updateModelTags(self, event = None):
        checked_modeltags = self.modeltags_lb.GetCheckedStrings()
        checked_modeltags_csv = ','.join(checked_modeltags)
        try:
            self.swmmrh.setModelTags(tags_csv = checked_modeltags_csv, model = self.current_model)
        except:
            exc_formatted = ''.join(traceback.format_exception(*sys.exc_info()))
            self.showError(exc_formatted)
            self.SetStatus('Failed to update model tags.')
        else:
            self.SetStatus('Updated model tags.')
            self.savemodeltags_btn.Disable()
            self.resetmodeltags_btn.Disable()
            self.enableCommit(True)
            self.refresh()

    def HandleUnsavedChanges(self, callafter, **kwargs):
        if self.updatedetails_btn.IsEnabled():
            dlg = wx.MessageDialog(self, 'Save changes to version information for ' + self.current_model + '?', 'Wait!', wx.YES_NO)
            if dlg.ShowModal() == wx.ID_YES:
                self.updateModelVersion()
        elif self.savemodeltags_btn.IsEnabled():
            dlg = wx.MessageDialog(self, 'Save changes to tags for ' + self.current_model + '?', 'Wait!', wx.YES_NO)
            if dlg.ShowModal() == wx.ID_YES:
                self.updateModelTags()
        callafter(**kwargs)

    def OnVersionSelected(self, event):
        if self.updatedetails_btn.IsEnabled():
            dlg = wx.MessageDialog(self, 'Save changes to version information for ' + self.current_model + '?', 'Wait!', wx.YES_NO)
            if dlg.ShowModal() == wx.ID_YES:
                self.updateModelVersion()
            else:
                self.fillVersionDetails()
        else:
            self.currentversion_ix = self.versions_lb.GetSelection()
            self.fillVersionDetails()
            if self.currentversion_ix != 0:
                self.tb.EnableTool(self.TOOL_CASCADE, False)
            else:
                self.tb.EnableTool(self.TOOL_CASCADE, True)

    def OnModelSelected(self, event):
        list_id = event.GetEventObject().GetId()
        if list_id == self.ID_USERLIST:
            modelslist = self.usermodels_list
            othermodelslist = self.excludedmodels_list
        else:
            modelslist = self.excludedmodels_list
            othermodelslist = self.usermodels_list
        other_modelitem = othermodelslist.GetFirstSelected()
        if other_modelitem != -1:
            othermodelslist.Select(other_modelitem, on=False)
        currentItem = event.m_itemIndex
        self.current_model = str(modelslist.GetItemText(currentItem))
        self.refreshModelVersions()
        self.addtag_text.Enable()
        self.refreshModelTags()

    def OnModelDeselected(self, event = None):
        if self.savemodeltags_btn.IsEnabled():
            dlg = wx.MessageDialog(self, 'Save changes to tags model tags?', 'Wait!', wx.YES_NO)
            if dlg.ShowModal() == wx.ID_YES:
                checked_modeltags = self.modeltags_lb.GetCheckedStrings()
                checkedtags_csv = ','.join(checked_modeltags)
                try:
                    self.swmmrh.setModelTags(tags_csv = checkedtags_csv, model = self.current_model)
                except:
                    exc_formatted = ''.join(traceback.format_exception(*sys.exc_info()))
                    self.showError(exc_formatted)
                    self.SetStatus('Failed to update model tags.')
                else:
                    self.SetStatus('Updated model tags.')
                    self.savemodeltags_btn.Disable()
                    self.resetmodeltags_btn.Disable()
                    self.enableCommit(True)
                    self.refresh()
        else:
            if self.updatedetails_btn.IsEnabled():
                dlg = wx.MessageDialog(self, 'Save changes to version information for ' + self.current_model + '?', 'Wait!', wx.YES_NO)
                if dlg.ShowModal() == wx.ID_YES:
                    update_kwargs = {'version_id'   : self.currentmodel_versiondata[self.currentversion_ix][self.IX_VERSIONID],
                                     'name'         : self.version_text.GetValue(), 
                                     'swmmbuild'    : self.swmmbuild_choice.GetStringSelection(),
                                     'docpath'      : self.doc_text.GetValue(),
                                     'desc'         : self.des_text.GetValue()}
                    self.updatedetails_btn.Disable()
                    self.resetdetails_btn.Disable()
                    try:
                        self.swmmrh.updateModelVersion(**update_kwargs)
                    except:
                        exc_formatted = ''.join(traceback.format_exception(*sys.exc_info()))
                        self.showError(exc_formatted)
                    else:
                        self.enableCommit(True)

            self.current_model = None
            self.currentmodel_versiondata = None
            self.currentversion_ix = None
            self.versions_lb.Clear()
            self.version_text.SetValue('')
            self.version_text.Disable()
            self.swmmbuild_choice.Clear()
            self.swmmbuild_choice.Disable()
            self.doc_text.SetValue('')
            self.doc_text.Disable()
            self.des_text.SetValue('')
            self.des_text.Disable()
            self.modeltags_lb.Clear()
            self.addtag_text.Disable()
            self.updatedetails_btn.Disable()
            self.resetdetails_btn.Disable()
            
            for id in self.version_tool_ids:
                self.tb.EnableTool(id, False)

    def OnUserModelActivated(self, event):
        activated_item = event.m_itemIndex
        model = self.usermodels_list.GetItemText(activated_item)
        self.meta(model)

    def openswmm(self):
        swmmgui = self.getConfig('swmmgui')
        if os.path.exists(swmmgui):
            versionid = self.currentmodel_versiondata[self.currentversion_ix][self.IX_VERSIONID]
            temp_path = os.path.join(os.environ.get('TEMP'), 'swmmr_temp.inp')
            threading.Thread(target = self.Listen, 
                             args = (self.swmmrh.exportModel, 
                                    {'model' : self.current_model, 'path' : temp_path, 'version_id' : versionid, 'preamble' : True}, 
                                    self.OnOpenSWMMComplete, 
                                    {'path' : temp_path})).start()
        else:
            self.swmmpath()

    def OnOpenSWMMComplete(self, exc, result, path):
        if exc:
            self.SetStatus('Failed to export .INP file.')
            self.showError(exc)
        else:
            self.SetStatus('Opening ' + self.current_model + ' in SWMM...')
            try:
                gui_path = self.getConfig('swmmgui')
                app = pywinauto.application.Application()
                app = app.start('"' + gui_path + '"')
                window_up = False
                while not window_up:
                    try:
                        app.__getitem__('SWMM 5').MenuSelect('File->Open')
                    except:
                        traceback.print_exc()
                        time.sleep(2)
                    else:
                        window_up = True
                app.OpenaProject.edit1.SetText(path)
                app.OpenaProject.Open.Click()
            except:
                exc_formatted = ''.join(traceback.format_exception(*sys.exc_info()))
                self.showError(exc_formatted)
                self.SetStatus('Failed to open ' + self.current_model + ' in SWMM.')
            else:
                self.SetStatus('Opened ' + self.current_model + ' in SWMM.')

        for id in self.version_tool_ids:
            self.tb.EnableTool(id, True)

    def export(self):
        versionname = self.currentmodel_versiondata[self.currentversion_ix][self.IX_VNAME]
        versionts = self.currentmodel_versiondata[self.currentversion_ix][self.IX_TS]
        wildcard = 'SWMM INP (*.inp)|*.inp'
        default_fname = '-'.join([self.current_model, 
                                 re.sub(' ', '_', versionname),
                                 versionts.strftime('%y%m%d')]) + '.inp'

        dlg = wx.FileDialog(self, message='Export .INP as',
                            defaultDir=self.getConfig('defaultdir'),
                            defaultFile=default_fname,
                            wildcard=wildcard,
                            style=wx.SAVE)
        dlg.CenterOnScreen()

        if dlg.ShowModal() == wx.ID_OK:
            path = str(dlg.GetPaths()[0])
            self.setConfig(defaultdir = os.path.basename(path))
            self.SetStatus('Exporting...')
            versionid = self.currentmodel_versiondata[self.currentversion_ix][self.IX_VERSIONID]
            threading.Thread(target = self.Listen, 
                             args = (self.swmmrh.exportModel, {'model' : self.current_model, 'path' : path, 'version_id' : versionid},
                                    self.OnExportComplete)).start()
            dlg.Destroy()

    def OnExportComplete(self, exc, result):
        if exc:
            self.SetStatus('Export failed.')
            self.showError(exc)
        else:
            self.SetStatus('Export complete.')

        for id in self.version_tool_ids:
            self.tb.EnableTool(id, True)

if __name__ == '__main__':
    app = wx.App(False)
    try:
        main_frame = MasterFrame()
    except:
        traceback.print_exc()
    app.MainLoop()

